From 49f1a6933cc22d1e703d631d5b861b8601ca2231 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Thu, 12 Jul 2018 23:13:47 +0200 Subject: Add fetchAsync util and RPCSubprovider --- packages/subproviders/package.json | 1 + packages/subproviders/src/globals.d.ts | 2 + packages/subproviders/src/index.ts | 1 + .../src/subproviders/rpc_subprovider.ts | 88 ++++++++++++++++++++++ .../subproviders/src/subproviders/subprovider.ts | 26 +++---- packages/types/src/index.ts | 12 +++ packages/utils/package.json | 4 +- packages/utils/src/fetchAsync.ts | 29 +++++++ packages/utils/src/index.ts | 2 + yarn.lock | 8 +- 10 files changed, 154 insertions(+), 19 deletions(-) create mode 100644 packages/subproviders/src/subproviders/rpc_subprovider.ts create mode 100644 packages/utils/src/fetchAsync.ts diff --git a/packages/subproviders/package.json b/packages/subproviders/package.json index bdc846e6b..796d87cf4 100644 --- a/packages/subproviders/package.json +++ b/packages/subproviders/package.json @@ -55,6 +55,7 @@ "ethereumjs-util": "^5.1.1", "ganache-core": "0xProject/ganache-core", "hdkey": "^0.7.1", + "json-rpc-error": "2.0.0", "lodash": "^4.17.4", "semaphore-async-await": "^1.5.1", "web3-provider-engine": "14.0.6" diff --git a/packages/subproviders/src/globals.d.ts b/packages/subproviders/src/globals.d.ts index 94e63a32d..6af4c7980 100644 --- a/packages/subproviders/src/globals.d.ts +++ b/packages/subproviders/src/globals.d.ts @@ -4,3 +4,5 @@ declare module '*.json' { export default json; /* tslint:enable */ } + +declare module 'json-rpc-error'; diff --git a/packages/subproviders/src/index.ts b/packages/subproviders/src/index.ts index 71d643f14..9a5597a06 100644 --- a/packages/subproviders/src/index.ts +++ b/packages/subproviders/src/index.ts @@ -10,6 +10,7 @@ export { FakeGasEstimateSubprovider } from './subproviders/fake_gas_estimate_sub export { SignerSubprovider } from './subproviders/signer'; export { RedundantSubprovider } from './subproviders/redundant_subprovider'; export { LedgerSubprovider } from './subproviders/ledger'; +export { RPCSubprovider } from './subproviders/rpc_subprovider'; export { GanacheSubprovider } from './subproviders/ganache'; export { Subprovider } from './subproviders/subprovider'; export { NonceTrackerSubprovider } from './subproviders/nonce_tracker'; diff --git a/packages/subproviders/src/subproviders/rpc_subprovider.ts b/packages/subproviders/src/subproviders/rpc_subprovider.ts new file mode 100644 index 000000000..87c522271 --- /dev/null +++ b/packages/subproviders/src/subproviders/rpc_subprovider.ts @@ -0,0 +1,88 @@ +import { assert } from '@0xproject/assert'; +import { StatusCodes } from '@0xproject/types'; +import { fetchAsync } from '@0xproject/utils'; +import { JSONRPCRequestPayload } from 'ethereum-types'; +import JsonRpcError = require('json-rpc-error'); + +import { Callback, ErrorCallback } from '../types'; + +import { Subprovider } from './subprovider'; + +/** + * This class implements the [web3-provider-engine](https://github.com/MetaMask/provider-engine) subprovider interface. + * It forwards on JSON RPC requests to the supplied `rpcUrl` endpoint + */ +export class RPCSubprovider extends Subprovider { + private _rpcUrl: string; + constructor(rpcUrl: string) { + super(); + assert.isString('rpcUrl', rpcUrl); + this._rpcUrl = rpcUrl; + } + /** + * This method conforms to the web3-provider-engine interface. + * It is called internally by the ProviderEngine when it is this subproviders + * turn to handle a JSON RPC request. + * @param payload JSON RPC payload + * @param next Callback to call if this subprovider decides not to handle the request + * @param end Callback to call if subprovider handled the request and wants to pass back the request. + */ + // tslint:disable-next-line:prefer-function-over-method async-suffix + public async handleRequest(payload: JSONRPCRequestPayload, next: Callback, end: ErrorCallback): Promise { + const finalPayload = Subprovider.createFinalPayload(payload); + const headers = new Headers({ + Accept: 'application/json', + 'Content-Type': 'application/json', + }); + const timeoutMs = 1000; + + let response; + try { + response = await fetchAsync( + this._rpcUrl, + { + method: 'POST', + headers, + body: JSON.stringify(finalPayload), + }, + timeoutMs, + ); + } catch (err) { + end(new JsonRpcError.InternalError(err)); + return; + } + + const text = await response.text(); + if (!response.ok) { + const statusCode = response.status; + switch (statusCode) { + case StatusCodes.MethodNotAllowed: + end(new JsonRpcError.MethodNotFound()); + return; + case StatusCodes.GatewayTimeout: + const errMsg = + 'Gateway timeout. The request took too long to process. This can happen when querying logs over too wide a block range.'; + const err = new Error(errMsg); + end(new JsonRpcError.InternalError(err)); + return; + default: + end(new JsonRpcError.InternalError(text)); + return; + } + } + + let data; + try { + data = JSON.parse(text); + } catch (err) { + end(new JsonRpcError.InternalError(err)); + return; + } + + if (data.error) { + end(data.error); + return; + } + end(null, data.result); + } +} diff --git a/packages/subproviders/src/subproviders/subprovider.ts b/packages/subproviders/src/subproviders/subprovider.ts index ff8378c4e..6f4a8f99e 100644 --- a/packages/subproviders/src/subproviders/subprovider.ts +++ b/packages/subproviders/src/subproviders/subprovider.ts @@ -9,18 +9,7 @@ import { Callback, ErrorCallback, JSONRPCRequestPayloadWithMethod } from '../typ export abstract class Subprovider { // tslint:disable-next-line:underscore-private-and-protected private engine!: Provider; - // Ported from: https://github.com/MetaMask/provider-engine/blob/master/util/random-id.js - private static _getRandomId(): number { - const extraDigits = 3; - const baseTen = 10; - // 13 time digits - const datePart = new Date().getTime() * Math.pow(baseTen, extraDigits); - // 3 random digits - const extraPart = Math.floor(Math.random() * Math.pow(baseTen, extraDigits)); - // 16 digits - return datePart + extraPart; - } - private static _createFinalPayload( + public static createFinalPayload( payload: Partial, ): Partial { const finalPayload = { @@ -32,6 +21,17 @@ export abstract class Subprovider { }; return finalPayload; } + // Ported from: https://github.com/MetaMask/provider-engine/blob/master/util/random-id.js + private static _getRandomId(): number { + const extraDigits = 3; + const baseTen = 10; + // 13 time digits + const datePart = new Date().getTime() * Math.pow(baseTen, extraDigits); + // 3 random digits + const extraPart = Math.floor(Math.random() * Math.pow(baseTen, extraDigits)); + // 16 digits + return datePart + extraPart; + } // tslint:disable-next-line:async-suffix public abstract async handleRequest( payload: JSONRPCRequestPayload, @@ -47,7 +47,7 @@ export abstract class Subprovider { * @returns JSON RPC response payload */ public async emitPayloadAsync(payload: Partial): Promise { - const finalPayload = Subprovider._createFinalPayload(payload); + const finalPayload = Subprovider.createFinalPayload(payload); const response = await promisify(this.engine.sendAsync, this.engine)(finalPayload); return response; } diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 03fa2fe8a..9b1731b9d 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -221,3 +221,15 @@ export enum RevertReason { ValueGreaterThanZero = 'VALUE_GREATER_THAN_ZERO', InvalidMsgValue = 'INVALID_MSG_VALUE', } + +export enum StatusCodes { + Success = 200, + NotFound = 404, + InternalError = 500, + MethodNotAllowed = 405, + GatewayTimeout = 504, +} + +export interface FetchRequest extends RequestInit { + timeout?: number; +} diff --git a/packages/utils/package.json b/packages/utils/package.json index 9168a3538..cb0989836 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -35,14 +35,14 @@ "typescript": "2.7.1" }, "dependencies": { - "ethereum-types": "^0.0.2", + "@0xproject/types": "^1.0.0", "@0xproject/typescript-typings": "^0.4.2", "@types/node": "^8.0.53", - "ethereumjs-util": "^5.1.1", "bignumber.js": "~4.1.0", "ethereum-types": "^0.0.2", "ethereumjs-util": "^5.1.1", "ethers": "3.0.22", + "isomorphic-fetch": "^2.2.1", "js-sha3": "^0.7.0", "lodash": "^4.17.4" }, diff --git a/packages/utils/src/fetchAsync.ts b/packages/utils/src/fetchAsync.ts new file mode 100644 index 000000000..7cb2c1759 --- /dev/null +++ b/packages/utils/src/fetchAsync.ts @@ -0,0 +1,29 @@ +import { FetchRequest } from '@0xproject/types'; +import 'isomorphic-fetch'; + +export const fetchAsync = async ( + endpoint: string, + options: FetchRequest, + timeoutMs: number = 20000, +): Promise => { + let finalOptions; + if ((process as any).browser === true) { + const controller = new AbortController(); + const signal = controller.signal; + setTimeout(() => { + controller.abort(); + }, timeoutMs); + finalOptions = { + signal, + ...options, + }; + } else { + finalOptions = { + timeout: timeoutMs, + ...options, + }; + } + + const response = await fetch(endpoint, finalOptions); + return response; +}; diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index fd102cecb..48fd6152e 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -8,3 +8,5 @@ export { logUtils } from './log_utils'; export { abiUtils } from './abi_utils'; export { NULL_BYTES } from './constants'; export { errorUtils } from './error_utils'; +export { fetchAsync } from './fetchAsync'; +export { FetchRequest } from '@0xproject/types'; diff --git a/yarn.lock b/yarn.lock index a8a897511..f6b6a32e0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4481,9 +4481,9 @@ ethereumjs-wallet@~0.6.0: utf8 "^2.1.1" uuid "^2.0.1" -ethers@3.0.22: - version "3.0.22" - resolved "https://registry.yarnpkg.com/ethers/-/ethers-3.0.22.tgz#7fab1ea16521705837aa43c15831877b2716b436" +ethers@0xproject/ethers.js#eip-838-reasons, ethers@3.0.22: + version "3.0.18" + resolved "https://codeload.github.com/0xproject/ethers.js/tar.gz/b91342bd200d142af0165d6befddf783c8ae8447" dependencies: aes-js "3.0.0" bn.js "^4.4.0" @@ -6899,7 +6899,7 @@ json-rpc-engine@^3.4.0, json-rpc-engine@^3.6.0: json-rpc-error "^2.0.0" promise-to-callback "^1.0.0" -json-rpc-error@^2.0.0: +json-rpc-error@2.0.0, json-rpc-error@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/json-rpc-error/-/json-rpc-error-2.0.0.tgz#a7af9c202838b5e905c7250e547f1aff77258a02" dependencies: -- cgit