Add fetchAsync util and RPCSubprovider

This commit is contained in:
Fabio Berger
2018-07-12 23:13:47 +02:00
parent a45a29432e
commit 49f1a6933c
10 changed files with 154 additions and 19 deletions

View File

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

View File

@@ -4,3 +4,5 @@ declare module '*.json' {
export default json;
/* tslint:enable */
}
declare module 'json-rpc-error';

View File

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

View File

@@ -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<void> {
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);
}
}

View File

@@ -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<JSONRPCRequestPayloadWithMethod>,
): Partial<JSONRPCRequestPayloadWithMethod> {
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<JSONRPCRequestPayloadWithMethod>): Promise<JSONRPCResponsePayload> {
const finalPayload = Subprovider._createFinalPayload(payload);
const finalPayload = Subprovider.createFinalPayload(payload);
const response = await promisify<JSONRPCResponsePayload>(this.engine.sendAsync, this.engine)(finalPayload);
return response;
}

View File

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

View File

@@ -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"
},

View File

@@ -0,0 +1,29 @@
import { FetchRequest } from '@0xproject/types';
import 'isomorphic-fetch';
export const fetchAsync = async (
endpoint: string,
options: FetchRequest,
timeoutMs: number = 20000,
): Promise<Response> => {
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;
};

View File

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

View File

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