123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248 |
- const _ = require('../../utils/underscore.js');
- const Q = require('bluebird');
- const log = require('../../utils/logger').create('method');
- const Windows = require('../../windows');
- const db = require('../../db');
- import ethereumNodeRemote from '../../ethereumNodeRemote';
- /**
- * Process a request.
- *
- * This is the base class for all specialized request processors.
- */
- module.exports = class BaseProcessor {
- constructor(name, ipcProviderBackend) {
- this._log = log.create(name);
- this._ipcProviderBackend = ipcProviderBackend;
- this.ERRORS = this._ipcProviderBackend.ERRORS;
- this.remoteIgnoreMethods = [
- 'eth_mining',
- 'eth_accounts',
- 'eth_coinbase',
- 'personal_newAccount',
- 'personal_signTransaction',
- 'eth_subscribe',
- 'eth_unsubscribe'
- ];
- }
- /**
- * Execute given request.
- * @param {Object} conn IPCProviderBackend connection data.
- * @param {Object|Array} payload payload
- * @return {Promise}
- */
- async exec(conn, payload) {
- this._log.trace('Execute request', payload);
- if (Array.isArray(payload)) {
- return await this._handleArrayExec(payload, conn);
- } else {
- return await this._handleObjectExec(payload, conn);
- }
- }
- _handleObjectExec(payload, conn) {
- return this._sendPayload(payload, conn);
- }
- async _handleArrayExec(payload, conn) {
- // If on local node, send batch transaction.
- // Otherwise, iterate through the batch to send over remote node since infura does not have batch support yet.
- const isRemote = store.getState().nodes.active === 'remote';
- if (!isRemote) {
- this._log.trace(
- `Sending batch request to local node: ${payload
- .map(req => {
- return req.payload;
- })
- .join(', ')}`
- );
- const ret = await conn.socket.send(payload, { fullResult: true });
- this._log.trace(
- `Result from batch request: ${payload
- .map(req => {
- return req.payload;
- })
- .join(', ')}`
- );
- return ret.result;
- }
- const result = [];
- payload.forEach(value => {
- result.push(this._handleObjectExec(value, conn));
- });
- return new Promise(async resolve => {
- resolve(await Promise.all(result));
- });
- }
- async _sendPayload(payload, conn) {
- if (this._shouldSendToRemote(payload, conn)) {
- this._log.trace(`Sending request to remote node: ${payload.method}`);
- const result = await this._sendToRemote(payload);
- this._log.trace(
- `Result from remote node: ${payload.method} (id: ${payload.id})`
- );
- return result;
- } else {
- this._log.trace(`Sending request to local node: ${payload.method}`);
- const result = await conn.socket.send(payload, { fullResult: true });
- this._log.trace(
- `Result from local node: ${payload.method} (id: ${payload.id})`
- );
- return result.result;
- }
- }
- _shouldSendToRemote(payload, conn) {
- // Do NOT send to the remote node if: (all conditions must be satisfied)
- // 1. the local node is synced
- const isRemote = store.getState().nodes.active === 'remote';
- if (!isRemote) {
- return false;
- }
- // 2. method is on the ignore list
- const method = payload.method;
- if (this.remoteIgnoreMethods.includes(method)) {
- return false;
- }
- // 3. the method is
- // net_peerCount | eth_syncing | eth_subscribe[syncing]
- // and is originating from the mist interface
- // dev: localhost:3000, production: app.asar/interface/index.html
- if (
- conn &&
- conn.owner &&
- conn.owner.history &&
- (conn.owner.history[0].startsWith('http://localhost:3000') ||
- conn.owner.history[0].indexOf('app.asar/interface/index.html') > -1)
- ) {
- if (
- method === 'net_peerCount' ||
- method === 'eth_syncing' ||
- (method === 'eth_subscribe' && payload.params[0] === 'syncing')
- ) {
- return false;
- }
- }
- return true;
- }
- _sendToRemote(payload, retry = false) {
- return new Promise(async (resolve, reject) => {
- const requestId = await ethereumNodeRemote.send(
- payload.method,
- payload.params
- );
- if (!requestId) {
- const errorMessage = `No request id for method ${payload.method} (${
- payload.params
- })`;
- reject(errorMessage);
- this._log.error(errorMessage);
- return;
- }
- const callback = data => {
- if (!data) {
- return;
- }
- try {
- data = JSON.parse(data);
- } catch (error) {
- const errorMessage = `Error parsing data: ${data}`;
- this._log.trace(errorMessage);
- reject(errorMessage);
- }
- if (data.id === requestId) {
- resolve(data);
- // TODO: remove listener
- // ethereumNodeRemote.ws.removeListener('message', callback);
- }
- };
- ethereumNodeRemote.ws.on('message', callback);
- });
- }
- _isAdminConnection(conn) {
- // main window or popupwindows - always allow requests
- const wnd = Windows.getById(conn.id);
- const tab = db.getCollection('UI_tabs').findOne({ webviewId: conn.id });
- return (
- (wnd && (wnd.type === 'main' || wnd.isPopup)) ||
- (tab && _.get(tab, 'permissions.admin') === true)
- );
- }
- /**
- Sanitize a request payload.
- This may modify the input payload object.
- @param {Object} conn The connection.
- @param {Object} payload The request payload.
- @param {Boolean} isPartOfABatch Whether it's part of a batch payload.
- */
- sanitizeRequestPayload(conn, payload, isPartOfABatch) {
- this._log.trace('Sanitize request payload', payload);
- this._sanitizeRequestResponsePayload(conn, payload, isPartOfABatch);
- }
- /**
- Sanitize a response payload.
- This may modify the input payload object.
- @param {Object} conn The connection.
- @param {Object} payload The request payload.
- @param {Boolean} isPartOfABatch Whether it's part of a batch payload.
- */
- sanitizeResponsePayload(conn, payload, isPartOfABatch) {
- this._log.trace('Sanitize response payload', payload);
- this._sanitizeRequestResponsePayload(conn, payload, isPartOfABatch);
- }
- /**
- Sanitize a request or response payload.
- This may modify the input payload object.
- @param {Object} conn The connection.
- @param {Object} payload The request payload.
- @param {Boolean} isPartOfABatch Whether it's part of a batch payload.
- */
- _sanitizeRequestResponsePayload(conn, payload, isPartOfABatch) {
- if (!_.isObject(payload)) {
- throw this.ERRORS.INVALID_PAYLOAD;
- }
- if (this._isAdminConnection(conn)) {
- return;
- }
- // prevent dapps from acccesing admin endpoints
- if (!/^eth_|^bzz_|^shh_|^net_|^web3_|^db_/.test(payload.method)) {
- delete payload.result;
- const err = _.clone(this.ERRORS.METHOD_DENIED);
- err.message = err.message.replace('__method__', `"${payload.method}"`);
- payload.error = err;
- }
- }
- };
|