base.js 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. const _ = require('../../utils/underscore.js');
  2. const Q = require('bluebird');
  3. const log = require('../../utils/logger').create('method');
  4. const Windows = require('../../windows');
  5. const db = require('../../db');
  6. import ethereumNodeRemote from '../../ethereumNodeRemote';
  7. /**
  8. * Process a request.
  9. *
  10. * This is the base class for all specialized request processors.
  11. */
  12. module.exports = class BaseProcessor {
  13. constructor(name, ipcProviderBackend) {
  14. this._log = log.create(name);
  15. this._ipcProviderBackend = ipcProviderBackend;
  16. this.ERRORS = this._ipcProviderBackend.ERRORS;
  17. this.remoteIgnoreMethods = [
  18. 'eth_mining',
  19. 'eth_accounts',
  20. 'eth_coinbase',
  21. 'personal_newAccount',
  22. 'personal_signTransaction',
  23. 'eth_subscribe',
  24. 'eth_unsubscribe'
  25. ];
  26. }
  27. /**
  28. * Execute given request.
  29. * @param {Object} conn IPCProviderBackend connection data.
  30. * @param {Object|Array} payload payload
  31. * @return {Promise}
  32. */
  33. async exec(conn, payload) {
  34. this._log.trace('Execute request', payload);
  35. if (Array.isArray(payload)) {
  36. return await this._handleArrayExec(payload, conn);
  37. } else {
  38. return await this._handleObjectExec(payload, conn);
  39. }
  40. }
  41. _handleObjectExec(payload, conn) {
  42. return this._sendPayload(payload, conn);
  43. }
  44. async _handleArrayExec(payload, conn) {
  45. // If on local node, send batch transaction.
  46. // Otherwise, iterate through the batch to send over remote node since infura does not have batch support yet.
  47. const isRemote = store.getState().nodes.active === 'remote';
  48. if (!isRemote) {
  49. this._log.trace(
  50. `Sending batch request to local node: ${payload
  51. .map(req => {
  52. return req.payload;
  53. })
  54. .join(', ')}`
  55. );
  56. const ret = await conn.socket.send(payload, { fullResult: true });
  57. this._log.trace(
  58. `Result from batch request: ${payload
  59. .map(req => {
  60. return req.payload;
  61. })
  62. .join(', ')}`
  63. );
  64. return ret.result;
  65. }
  66. const result = [];
  67. payload.forEach(value => {
  68. result.push(this._handleObjectExec(value, conn));
  69. });
  70. return new Promise(async resolve => {
  71. resolve(await Promise.all(result));
  72. });
  73. }
  74. async _sendPayload(payload, conn) {
  75. if (this._shouldSendToRemote(payload, conn)) {
  76. this._log.trace(`Sending request to remote node: ${payload.method}`);
  77. const result = await this._sendToRemote(payload);
  78. this._log.trace(
  79. `Result from remote node: ${payload.method} (id: ${payload.id})`
  80. );
  81. return result;
  82. } else {
  83. this._log.trace(`Sending request to local node: ${payload.method}`);
  84. const result = await conn.socket.send(payload, { fullResult: true });
  85. this._log.trace(
  86. `Result from local node: ${payload.method} (id: ${payload.id})`
  87. );
  88. return result.result;
  89. }
  90. }
  91. _shouldSendToRemote(payload, conn) {
  92. // Do NOT send to the remote node if: (all conditions must be satisfied)
  93. // 1. the local node is synced
  94. const isRemote = store.getState().nodes.active === 'remote';
  95. if (!isRemote) {
  96. return false;
  97. }
  98. // 2. method is on the ignore list
  99. const method = payload.method;
  100. if (this.remoteIgnoreMethods.includes(method)) {
  101. return false;
  102. }
  103. // 3. the method is
  104. // net_peerCount | eth_syncing | eth_subscribe[syncing]
  105. // and is originating from the mist interface
  106. // dev: localhost:3000, production: app.asar/interface/index.html
  107. if (
  108. conn &&
  109. conn.owner &&
  110. conn.owner.history &&
  111. (conn.owner.history[0].startsWith('http://localhost:3000') ||
  112. conn.owner.history[0].indexOf('app.asar/interface/index.html') > -1)
  113. ) {
  114. if (
  115. method === 'net_peerCount' ||
  116. method === 'eth_syncing' ||
  117. (method === 'eth_subscribe' && payload.params[0] === 'syncing')
  118. ) {
  119. return false;
  120. }
  121. }
  122. return true;
  123. }
  124. _sendToRemote(payload, retry = false) {
  125. return new Promise(async (resolve, reject) => {
  126. const requestId = await ethereumNodeRemote.send(
  127. payload.method,
  128. payload.params
  129. );
  130. if (!requestId) {
  131. const errorMessage = `No request id for method ${payload.method} (${
  132. payload.params
  133. })`;
  134. reject(errorMessage);
  135. this._log.error(errorMessage);
  136. return;
  137. }
  138. const callback = data => {
  139. if (!data) {
  140. return;
  141. }
  142. try {
  143. data = JSON.parse(data);
  144. } catch (error) {
  145. const errorMessage = `Error parsing data: ${data}`;
  146. this._log.trace(errorMessage);
  147. reject(errorMessage);
  148. }
  149. if (data.id === requestId) {
  150. resolve(data);
  151. // TODO: remove listener
  152. // ethereumNodeRemote.ws.removeListener('message', callback);
  153. }
  154. };
  155. ethereumNodeRemote.ws.on('message', callback);
  156. });
  157. }
  158. _isAdminConnection(conn) {
  159. // main window or popupwindows - always allow requests
  160. const wnd = Windows.getById(conn.id);
  161. const tab = db.getCollection('UI_tabs').findOne({ webviewId: conn.id });
  162. return (
  163. (wnd && (wnd.type === 'main' || wnd.isPopup)) ||
  164. (tab && _.get(tab, 'permissions.admin') === true)
  165. );
  166. }
  167. /**
  168. Sanitize a request payload.
  169. This may modify the input payload object.
  170. @param {Object} conn The connection.
  171. @param {Object} payload The request payload.
  172. @param {Boolean} isPartOfABatch Whether it's part of a batch payload.
  173. */
  174. sanitizeRequestPayload(conn, payload, isPartOfABatch) {
  175. this._log.trace('Sanitize request payload', payload);
  176. this._sanitizeRequestResponsePayload(conn, payload, isPartOfABatch);
  177. }
  178. /**
  179. Sanitize a response payload.
  180. This may modify the input payload object.
  181. @param {Object} conn The connection.
  182. @param {Object} payload The request payload.
  183. @param {Boolean} isPartOfABatch Whether it's part of a batch payload.
  184. */
  185. sanitizeResponsePayload(conn, payload, isPartOfABatch) {
  186. this._log.trace('Sanitize response payload', payload);
  187. this._sanitizeRequestResponsePayload(conn, payload, isPartOfABatch);
  188. }
  189. /**
  190. Sanitize a request or response payload.
  191. This may modify the input payload object.
  192. @param {Object} conn The connection.
  193. @param {Object} payload The request payload.
  194. @param {Boolean} isPartOfABatch Whether it's part of a batch payload.
  195. */
  196. _sanitizeRequestResponsePayload(conn, payload, isPartOfABatch) {
  197. if (!_.isObject(payload)) {
  198. throw this.ERRORS.INVALID_PAYLOAD;
  199. }
  200. if (this._isAdminConnection(conn)) {
  201. return;
  202. }
  203. // prevent dapps from acccesing admin endpoints
  204. if (!/^eth_|^bzz_|^shh_|^net_|^web3_|^db_/.test(payload.method)) {
  205. delete payload.result;
  206. const err = _.clone(this.ERRORS.METHOD_DENIED);
  207. err.message = err.message.replace('__method__', `"${payload.method}"`);
  208. payload.error = err;
  209. }
  210. }
  211. };