diff --git a/.eslintrc.js b/.eslintrc.js index 1ef7f4f52..9379efb55 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -258,14 +258,15 @@ module.exports = { files: [ '**/__snapshots__/*.snap', 'app/scripts/controllers/network/**/*.test.js', + 'app/scripts/controllers/network/provider-api-tests/*.js', 'app/scripts/controllers/permissions/**/*.test.js', 'app/scripts/lib/**/*.test.js', 'app/scripts/migrations/*.test.js', 'app/scripts/platforms/*.test.js', 'development/**/*.test.js', 'shared/**/*.test.js', - 'test/jest/*.js', 'test/helpers/*.js', + 'test/jest/*.js', 'ui/**/*.test.js', 'ui/__mocks__/*.js', ], diff --git a/app/scripts/controllers/network/createInfuraClient.test.js b/app/scripts/controllers/network/createInfuraClient.test.js new file mode 100644 index 000000000..0d7ac9557 --- /dev/null +++ b/app/scripts/controllers/network/createInfuraClient.test.js @@ -0,0 +1,332 @@ +/** + * @jest-environment node + */ + +import { withInfuraClient } from './provider-api-tests/helpers'; +import { + testsForRpcMethodNotHandledByMiddleware, + testsForRpcMethodAssumingNoBlockParam, + testsForRpcMethodsThatCheckForBlockHashInResponse, + testsForRpcMethodSupportingBlockParam, +} from './provider-api-tests/shared-tests'; + +describe('createInfuraClient', () => { + // Infura documentation: + // Ethereum JSON-RPC spec: + + describe('RPC methods supported by Infura and listed in the JSON-RPC spec', () => { + describe('eth_accounts', () => { + testsForRpcMethodNotHandledByMiddleware('eth_accounts', { + numberOfParameters: 0, + }); + }); + + describe('eth_blockNumber', () => { + testsForRpcMethodAssumingNoBlockParam('eth_blockNumber'); + }); + + describe('eth_call', () => { + testsForRpcMethodSupportingBlockParam('eth_call', { + blockParamIndex: 1, + }); + }); + + describe('eth_chainId', () => { + it('does not hit Infura, instead returning the chain id that maps to the Infura network, as a hex string', async () => { + const chainId = await withInfuraClient( + { network: 'ropsten' }, + ({ makeRpcCall }) => { + return makeRpcCall({ + method: 'eth_chainId', + }); + }, + ); + + expect(chainId).toStrictEqual('0x3'); + }); + }); + + describe('eth_coinbase', () => { + testsForRpcMethodNotHandledByMiddleware('eth_coinbase', { + numberOfParameters: 0, + }); + }); + + describe('eth_estimateGas', () => { + testsForRpcMethodAssumingNoBlockParam('eth_estimateGas'); + }); + + describe('eth_feeHistory', () => { + testsForRpcMethodNotHandledByMiddleware('eth_feeHistory', { + numberOfParameters: 3, + }); + }); + + describe('eth_getBalance', () => { + testsForRpcMethodSupportingBlockParam('eth_getBalance', { + blockParamIndex: 1, + }); + }); + + describe('eth_gasPrice', () => { + testsForRpcMethodAssumingNoBlockParam('eth_gasPrice'); + }); + + describe('eth_getBlockByHash', () => { + testsForRpcMethodAssumingNoBlockParam('eth_getBlockByHash'); + }); + + describe('eth_getBlockByNumber', () => { + testsForRpcMethodSupportingBlockParam('eth_getBlockByNumber', { + blockParamIndex: 0, + }); + }); + + describe('eth_getBlockTransactionCountByHash', () => { + testsForRpcMethodAssumingNoBlockParam( + 'eth_getBlockTransactionCountByHash', + ); + }); + + describe('eth_getBlockTransactionCountByNumber', () => { + // NOTE: eth_getBlockTransactionCountByNumber does take a block param at + // the 0th index, but this is not handled by our cache middleware + // currently + testsForRpcMethodAssumingNoBlockParam( + 'eth_getBlockTransactionCountByNumber', + ); + }); + + describe('eth_getCode', () => { + testsForRpcMethodSupportingBlockParam('eth_getCode', { + blockParamIndex: 1, + }); + }); + + describe('eth_getFilterChanges', () => { + testsForRpcMethodNotHandledByMiddleware('eth_getFilterChanges', { + numberOfParameters: 1, + }); + }); + + describe('eth_getFilterLogs', () => { + testsForRpcMethodAssumingNoBlockParam('eth_getFilterLogs'); + }); + + describe('eth_getLogs', () => { + testsForRpcMethodNotHandledByMiddleware('eth_getLogs', { + numberOfParameters: 1, + }); + }); + + describe('eth_getStorageAt', () => { + testsForRpcMethodSupportingBlockParam('eth_getStorageAt', { + blockParamIndex: 2, + }); + }); + + describe('eth_getTransactionByBlockHashAndIndex', () => { + testsForRpcMethodAssumingNoBlockParam( + 'eth_getTransactionByBlockHashAndIndex', + ); + }); + + describe('eth_getTransactionByBlockNumberAndIndex', () => { + // NOTE: eth_getTransactionByBlockNumberAndIndex does take a block param + // at the 0th index, but this is not handled by our cache middleware + // currently + testsForRpcMethodAssumingNoBlockParam( + 'eth_getTransactionByBlockNumberAndIndex', + ); + }); + + describe('eth_getTransactionByHash', () => { + testsForRpcMethodsThatCheckForBlockHashInResponse( + 'eth_getTransactionByHash', + ); + }); + + describe('eth_getTransactionCount', () => { + testsForRpcMethodSupportingBlockParam('eth_getTransactionCount', { + blockParamIndex: 1, + }); + }); + + describe('eth_getTransactionReceipt', () => { + testsForRpcMethodsThatCheckForBlockHashInResponse( + 'eth_getTransactionReceipt', + ); + }); + + describe('eth_getUncleByBlockHashAndIndex', () => { + testsForRpcMethodAssumingNoBlockParam('eth_getUncleByBlockHashAndIndex'); + }); + + describe('eth_getUncleByBlockNumberAndIndex', () => { + // NOTE: eth_getUncleByBlockNumberAndIndex does take a block param at the + // 0th index, but this is not handled by our cache middleware currently + testsForRpcMethodAssumingNoBlockParam( + 'eth_getUncleByBlockNumberAndIndex', + ); + }); + + describe('eth_getUncleCountByBlockHash', () => { + testsForRpcMethodAssumingNoBlockParam('eth_getUncleCountByBlockHash'); + }); + + describe('eth_getUncleCountByBlockNumber', () => { + // NOTE: eth_getUncleCountByBlockNumber does take a block param at the 0th + // index, but this is not handled by our cache middleware currently + testsForRpcMethodAssumingNoBlockParam('eth_getUncleCountByBlockNumber'); + }); + + describe('eth_getWork', () => { + testsForRpcMethodNotHandledByMiddleware('eth_getWork', { + numberOfParameters: 0, + }); + }); + + describe('eth_hashrate', () => { + testsForRpcMethodNotHandledByMiddleware('eth_hashrate', { + numberOfParameters: 0, + }); + }); + + describe('eth_mining', () => { + testsForRpcMethodNotHandledByMiddleware('eth_mining', { + numberOfParameters: 0, + }); + }); + + describe('eth_newBlockFilter', () => { + testsForRpcMethodNotHandledByMiddleware('eth_newBlockFilter', { + numberOfParameters: 0, + }); + }); + + describe('eth_newFilter', () => { + testsForRpcMethodNotHandledByMiddleware('eth_newFilter', { + numberOfParameters: 1, + }); + }); + + describe('eth_newPendingTransactionFilter', () => { + testsForRpcMethodNotHandledByMiddleware( + 'eth_newPendingTransactionFilter', + { numberOfParameters: 0 }, + ); + }); + + describe('eth_protocolVersion', () => { + testsForRpcMethodAssumingNoBlockParam('eth_protocolVersion'); + }); + + describe('eth_sendRawTransaction', () => { + testsForRpcMethodNotHandledByMiddleware('eth_sendRawTransaction', { + numberOfParameters: 1, + }); + }); + + describe('eth_sendTransaction', () => { + testsForRpcMethodNotHandledByMiddleware('eth_sendTransaction', { + numberOfParameters: 1, + }); + }); + + describe('eth_sign', () => { + testsForRpcMethodNotHandledByMiddleware('eth_sign', { + numberOfParameters: 2, + }); + }); + + describe('eth_submitWork', () => { + testsForRpcMethodNotHandledByMiddleware('eth_submitWork', { + numberOfParameters: 3, + }); + }); + + describe('eth_syncing', () => { + testsForRpcMethodNotHandledByMiddleware('eth_syncing', { + numberOfParameters: 0, + }); + }); + + describe('eth_uninstallFilter', () => { + testsForRpcMethodNotHandledByMiddleware('eth_uninstallFilter', { + numberOfParameters: 1, + }); + }); + }); + + describe('RPC methods supported by Infura but not listed in the JSON-RPC spec', () => { + describe('eth_subscribe', () => { + testsForRpcMethodNotHandledByMiddleware('eth_subscribe', { + numberOfParameters: 1, + }); + }); + + describe('eth_unsubscribe', () => { + testsForRpcMethodNotHandledByMiddleware('eth_unsubscribe', { + numberOfParameters: 1, + }); + }); + + describe('net_listening', () => { + testsForRpcMethodNotHandledByMiddleware('net_listening', { + numberOfParameters: 0, + }); + }); + + describe('net_peerCount', () => { + testsForRpcMethodNotHandledByMiddleware('net_peerCount', { + numberOfParameters: 0, + }); + }); + + describe('net_version', () => { + it('does not hit Infura, instead returning the chain id that maps to the Infura network, as a decimal string', async () => { + const chainId = await withInfuraClient( + { network: 'ropsten' }, + ({ makeRpcCall }) => { + return makeRpcCall({ + method: 'net_version', + }); + }, + ); + + expect(chainId).toStrictEqual('3'); + }); + }); + + describe('parity_nextNonce', () => { + testsForRpcMethodNotHandledByMiddleware('parity_nextNonce', { + numberOfParameters: 1, + }); + }); + + describe('web3_clientVersion', () => { + testsForRpcMethodAssumingNoBlockParam('web3_clientVersion'); + }); + }); + + // NOTE: The following methods are omitted because although they are listed in + // the Ethereum spec, they do not seem to be supported by Infura: + // + // - debug_getBadBlocks + // - debug_getRawBlock + // - debug_getRawHeader + // - debug_getRawReceipts + // - eth_createAccessList + // - eth_compileLLL + // - eth_compileSerpent + // - eth_compileSolidity + // - eth_getCompilers + // - eth_getProof + // - eth_maxPriorityFeePerGas + // - eth_submitHashrate + // - web3_sha3 + + testsForRpcMethodNotHandledByMiddleware('custom_rpc_method', { + numberOfParameters: 1, + }); +}); diff --git a/app/scripts/controllers/network/provider-api-tests/helpers.js b/app/scripts/controllers/network/provider-api-tests/helpers.js new file mode 100644 index 000000000..9b4c22460 --- /dev/null +++ b/app/scripts/controllers/network/provider-api-tests/helpers.js @@ -0,0 +1,333 @@ +import nock from 'nock'; +import sinon from 'sinon'; +import { JsonRpcEngine } from 'json-rpc-engine'; +import { providerFromEngine } from 'eth-json-rpc-middleware'; +import EthQuery from 'eth-query'; +import createInfuraClient from '../createInfuraClient'; + +/** + * @typedef {import('nock').Scope} NockScope + * + * A object returned by `nock(...)` for mocking requests to a particular base + * URL. + */ + +/** + * @typedef {{makeRpcCall: (request: Partial) => Promise, makeRpcCallsInSeries: (requests: Partial[]) => Promise}} InfuraClient + * + * Provides methods to interact with the suite of middleware that + * `createInfuraClient` exposes. + */ + +/** + * @typedef {{network: string}} WithInfuraClientOptions + * + * The options bag that `withInfuraClient` takes. + */ + +/** + * @typedef {(client: InfuraClient) => Promise} WithInfuraClientCallback + * + * The callback that `withInfuraClient` takes. + */ + +/** + * @typedef {[WithInfuraClientOptions, WithInfuraClientCallback] | [WithInfuraClientCallback]} WithInfuraClientArgs + * + * The arguments to `withInfuraClient`. + */ + +/** + * @typedef {{ nockScope: NockScope, blockNumber: string }} MockNextBlockTrackerRequestOptions + * + * The options to `mockNextBlockTrackerRequest`. + */ + +/** + * @typedef {{ nockScope: NockScope, request: object, response: object, delay?: number }} MockSuccessfulInfuraRpcCallOptions + * + * The options to `mockSuccessfulInfuraRpcCall`. + */ + +/** + * @typedef {{mockNextBlockTrackerRequest: (options: Omit) => void, mockSuccessfulInfuraRpcCall: (options: Omit) => NockScope}} InfuraCommunications + * + * Provides methods to mock different kinds of requests to Infura. + */ + +/** + * @typedef {{network: string}} MockingInfuraCommunicationsOptions + * + * The options bag that `mockingInfuraCommunications` takes. + */ + +/** + * @typedef {(comms: InfuraCommunications) => Promise} MockingInfuraCommunicationsCallback + * + * The callback that `mockingInfuraCommunications` takes. + */ + +/** + * @typedef {[MockingInfuraCommunicationsOptions, MockingInfuraCommunicationsCallback] | [MockingInfuraCommunicationsCallback]} MockingInfuraCommunicationsArgs + * + * The arguments to `mockingInfuraCommunications`. + */ + +const INFURA_PROJECT_ID = 'abc123'; +const DEFAULT_LATEST_BLOCK_NUMBER = '0x42'; + +/** + * If you're having trouble writing a test and you're wondering why the test + * keeps failing, you can set `process.env.DEBUG_PROVIDER_TESTS` to `1`. This + * will turn on some extra logging. + * + * @param {any[]} args - The arguments that `console.log` takes. + */ +function debug(...args) { + if (process.env.DEBUG_PROVIDER_TESTS === '1') { + console.log(...args); + } +} + +/** + * Builds a Nock scope object for mocking requests to a particular network that + * Infura supports. + * + * @param {object} options - The options. + * @param {string} options.network - The Infura network you're testing with + * (default: "mainnet"). + * @returns {NockScope} The nock scope. + */ +function buildScopeForMockingInfuraRequests({ network = 'mainnet' } = {}) { + return nock(`https://${network}.infura.io`).filteringRequestBody((body) => { + const copyOfBody = JSON.parse(body); + // some ids are random, so remove them entirely from the request to + // make it possible to mock these requests + delete copyOfBody.id; + return JSON.stringify(copyOfBody); + }); +} + +/** + * Mocks the next request for the latest block that the block tracker will make. + * + * @param {MockNextBlockTrackerRequestOptions} args - The arguments. + * @param {NockScope} args.nockScope - A nock scope (a set of mocked requests + * scoped to a certain base URL). + * @param {string} args.blockNumber - The block number that the block tracker + * should report, as a 0x-prefixed hex string. + */ +async function mockNextBlockTrackerRequest({ + nockScope, + blockNumber = DEFAULT_LATEST_BLOCK_NUMBER, +}) { + await mockSuccessfulInfuraRpcCall({ + nockScope, + request: { method: 'eth_blockNumber', params: [] }, + response: { result: blockNumber }, + }); +} + +/** + * Mocks a JSON-RPC request sent to Infura with the given response. + * + * @param {MockSuccessfulInfuraRpcCallOptions} args - The arguments. + * @param {NockScope} args.nockScope - A nock scope (a set of mocked requests + * scoped to a certain base URL). + * @param {object} args.request - The request data. + * @param {object} args.response - The response that the request should have. + * @param {number} args.delay - The amount of time that should pass before the + * request resolves with the response. + * @returns {NockScope} The nock scope. + */ +function mockSuccessfulInfuraRpcCall({ nockScope, request, response, delay }) { + // eth-query always passes `params`, so even if we don't supply this property + // for consistency with makeRpcCall, assume that the `body` contains it + const { method, params = [], ...rest } = request; + const completeResponse = { + id: 1, + jsonrpc: '2.0', + ...response, + }; + const nockRequest = nockScope.post(`/v3/${INFURA_PROJECT_ID}`, { + jsonrpc: '2.0', + method, + params, + ...rest, + }); + + if (delay !== undefined) { + nockRequest.delay(delay); + } + + return nockRequest.reply(200, completeResponse); +} + +/** + * Makes a JSON-RPC call through the given eth-query object. + * + * @param {any} ethQuery - The eth-query object. + * @param {object} request - The request data. + * @returns {Promise} A promise that either resolves with the result from + * the JSON-RPC response if it is successful or rejects with the error from the + * JSON-RPC response otherwise. + */ +function makeRpcCall(ethQuery, request) { + return new Promise((resolve, reject) => { + debug('[makeRpcCall] making request', request); + ethQuery.sendAsync(request, (error, result) => { + debug('[makeRpcCall > ethQuery handler] error', error, 'result', result); + if (error) { + reject(error); + } else { + resolve(result); + } + }); + }); +} + +/** + * Sets up request mocks for requests to Infura. + * + * @param {MockingInfuraCommunicationsArgs} args - Either an options bag + a + * function, or just a function. The options bag, at the moment, may contain + * `network` (that is, the Infura network; defaults to "mainnet"). The function + * is called with an object that allows you to mock different kinds of requests. + * @returns {Promise} The return value of the given function. + */ +export async function withMockedInfuraCommunications(...args) { + const [options, fn] = args.length === 2 ? args : [{}, args[0]]; + const { network = 'mainnet' } = options; + + const nockScope = buildScopeForMockingInfuraRequests({ network }); + const curriedMockNextBlockTrackerRequest = (localOptions) => + mockNextBlockTrackerRequest({ nockScope, ...localOptions }); + const curriedMockSuccessfulInfuraRpcCall = (localOptions) => + mockSuccessfulInfuraRpcCall({ nockScope, ...localOptions }); + const comms = { + mockNextBlockTrackerRequest: curriedMockNextBlockTrackerRequest, + mockSuccessfulInfuraRpcCall: curriedMockSuccessfulInfuraRpcCall, + }; + + try { + return await fn(comms); + } finally { + nock.isDone(); + nock.cleanAll(); + } +} + +/** + * Builds a provider from the Infura middleware along with a block tracker, runs + * the given function with those two things, and then ensures the block tracker + * is stopped at the end. + * + * @param {WithInfuraClientArgs} args - Either an options bag + a function, or + * just a function. The options bag, at the moment, may contain `network` (that + * is, the Infura network; defaults to "mainnet"). The function is called with + * an object that allows you to interact with the client via a couple of methods + * on that object. + * @returns {Promise} The return value of the given function. + */ +export async function withInfuraClient(...args) { + const [options, fn] = args.length === 2 ? args : [{}, args[0]]; + const { network = 'mainnet' } = options; + + const { networkMiddleware, blockTracker } = createInfuraClient({ + network, + projectId: INFURA_PROJECT_ID, + }); + + const engine = new JsonRpcEngine(); + engine.push(networkMiddleware); + const provider = providerFromEngine(engine); + const ethQuery = new EthQuery(provider); + + const curriedMakeRpcCall = (request) => makeRpcCall(ethQuery, request); + const makeRpcCallsInSeries = async (requests) => { + const responses = []; + for (const request of requests) { + responses.push(await curriedMakeRpcCall(request)); + } + return responses; + }; + // Faking timers ends up doing two things: + // 1. Halting the block tracker (which depends on `setTimeout` to periodically + // request the latest block) set up in `eth-json-rpc-middleware` + // 2. Halting the retry logic in `@metamask/eth-json-rpc-infura` (which also + // depends on `setTimeout`) + const clock = sinon.useFakeTimers(); + const client = { + makeRpcCall: curriedMakeRpcCall, + makeRpcCallsInSeries, + clock, + }; + + try { + return await fn(client); + } finally { + await blockTracker.destroy(); + + clock.restore(); + } +} + +/** + * Some JSON-RPC endpoints take a "block" param (example: `eth_blockNumber`) + * which can optionally be left out. Additionally, the endpoint may support some + * number of arguments, although the "block" param will always be last, even if + * it is optional. Given this, this function builds a mock `params` array for + * such an endpoint, filling it with arbitrary values, but with the "block" + * param missing. + * + * @param {number} index - The index within the `params` array where the "block" + * param *would* appear. + * @returns {string[]} The mock params. + */ +export function buildMockParamsWithoutBlockParamAt(index) { + const params = []; + for (let i = 0; i < index; i++) { + params.push('some value'); + } + return params; +} + +/** + * Some JSON-RPC endpoints take a "block" param (example: `eth_blockNumber`) + * which can optionally be left out. Additionally, the endpoint may support some + * number of arguments, although the "block" param will always be last, even if + * it is optional. Given this, this function builds a `params` array for such an + * endpoint with the given "block" param added at the end. + * + * @param {number} index - The index within the `params` array to add the + * "block" param. + * @param {any} blockParam - The desired "block" param to add. + * @returns {any[]} The mock params. + */ +export function buildMockParamsWithBlockParamAt(index, blockParam) { + const params = buildMockParamsWithoutBlockParamAt(index); + params.push(blockParam); + return params; +} + +/** + * Returns a partial JSON-RPC request object, with the "block" param replaced + * with the given value. + * + * @param {object} request - The request object. + * @param {string} request.method - The request method. + * @param {params} [request.params] - The request params. + * @param {number} blockParamIndex - The index within the `params` array of the + * block param. + * @param {any} blockParam - The desired block param value. + * @returns {object} The updated request object. + */ +export function buildRequestWithReplacedBlockParam( + { method, params = [] }, + blockParamIndex, + blockParam, +) { + const updatedParams = params.slice(); + updatedParams[blockParamIndex] = blockParam; + return { method, params: updatedParams }; +} diff --git a/app/scripts/controllers/network/provider-api-tests/shared-tests.js b/app/scripts/controllers/network/provider-api-tests/shared-tests.js new file mode 100644 index 000000000..aafa2155a --- /dev/null +++ b/app/scripts/controllers/network/provider-api-tests/shared-tests.js @@ -0,0 +1,708 @@ +/* eslint-disable jest/require-top-level-describe, jest/no-export, jest/no-identical-title */ + +import { fill } from 'lodash'; +import { + withMockedInfuraCommunications, + withInfuraClient, + buildMockParamsWithoutBlockParamAt, + buildMockParamsWithBlockParamAt, + buildRequestWithReplacedBlockParam, +} from './helpers'; + +export function testsForRpcMethodNotHandledByMiddleware( + method, + { numberOfParameters }, +) { + it('attempts to pass the request off to Infura', async () => { + const request = { + method, + params: fill(Array(numberOfParameters), 'some value'), + }; + const expectedResult = 'the result'; + + await withMockedInfuraCommunications(async (comms) => { + // The first time a block-cacheable request is made, the latest block + // number is retrieved through the block tracker first. It doesn't + // matter what this is — it's just used as a cache key. + comms.mockNextBlockTrackerRequest(); + comms.mockSuccessfulInfuraRpcCall({ + request, + response: { result: expectedResult }, + }); + const actualResult = await withInfuraClient(({ makeRpcCall }) => + makeRpcCall(request), + ); + + expect(actualResult).toStrictEqual(expectedResult); + }); + }); +} + +/** + * Defines tests which exercise the behavior exhibited by an RPC method which is + * assumed to not take a block parameter. Even if it does, the value of this + * parameter will not be used in determining how to cache the method. + * + * @param method - The name of the RPC method under test. + */ +export function testsForRpcMethodAssumingNoBlockParam(method) { + it('does not hit Infura more than once for identical requests', async () => { + const requests = [{ method }, { method }]; + const mockResults = ['first result', 'second result']; + + await withMockedInfuraCommunications(async (comms) => { + // The first time a block-cacheable request is made, the latest block + // number is retrieved through the block tracker first. It doesn't + // matter what this is — it's just used as a cache key. + comms.mockNextBlockTrackerRequest(); + comms.mockSuccessfulInfuraRpcCall({ + request: requests[0], + response: { result: mockResults[0] }, + }); + const results = await withInfuraClient(({ makeRpcCallsInSeries }) => + makeRpcCallsInSeries(requests), + ); + + expect(results).toStrictEqual([mockResults[0], mockResults[0]]); + }); + }); + + it('hits Infura and does not reuse the result of a previous request if the latest block number was updated since', async () => { + const requests = [{ method }, { method }]; + const mockResults = ['first result', 'second result']; + + await withMockedInfuraCommunications(async (comms) => { + // Note that we have to mock these requests in a specific order. The + // first block tracker request occurs because of the first RPC request. + // The second block tracker request, however, does not occur because of + // the second RPC request, but rather because we call `clock.runAll()` + // below. + comms.mockNextBlockTrackerRequest({ blockNumber: '0x1' }); + comms.mockSuccessfulInfuraRpcCall({ + request: requests[0], + response: { result: mockResults[0] }, + }); + comms.mockNextBlockTrackerRequest({ blockNumber: '0x2' }); + comms.mockSuccessfulInfuraRpcCall({ + request: requests[1], + response: { result: mockResults[1] }, + }); + + const results = await withInfuraClient(async (client) => { + const firstResult = await client.makeRpcCall(requests[0]); + // Proceed to the next iteration of the block tracker so that a new + // block is fetched and the current block is updated. + client.clock.runAll(); + const secondResult = await client.makeRpcCall(requests[1]); + return [firstResult, secondResult]; + }); + + expect(results).toStrictEqual(mockResults); + }); + }); + + it.each([null, undefined, '\u003cnil\u003e'])( + 'does not reuse the result of a previous request if it was `%s`', + async (emptyValue) => { + const requests = [{ method }, { method }]; + const mockResults = [emptyValue, 'some result']; + + await withMockedInfuraCommunications(async (comms) => { + // The first time a block-cacheable request is made, the latest block + // number is retrieved through the block tracker first. It doesn't + // matter what this is — it's just used as a cache key. + comms.mockNextBlockTrackerRequest(); + comms.mockSuccessfulInfuraRpcCall({ + request: requests[0], + response: { result: mockResults[0] }, + }); + comms.mockSuccessfulInfuraRpcCall({ + request: requests[1], + response: { result: mockResults[1] }, + }); + + const results = await withInfuraClient(({ makeRpcCallsInSeries }) => + makeRpcCallsInSeries(requests), + ); + + expect(results).toStrictEqual(mockResults); + }); + }, + ); +} + +/** + * Defines tests which exercise the behavior exhibited by an RPC method that + * use `blockHash` in the response data to determine whether the response is + * cacheable. + * + * @param method - The name of the RPC method under test. + */ +export function testsForRpcMethodsThatCheckForBlockHashInResponse(method) { + it('does not hit Infura more than once for identical requests and it has a valid blockHash', async () => { + const requests = [{ method }, { method }]; + const mockResults = [{ blockHash: '0x100' }, { blockHash: '0x200' }]; + + await withMockedInfuraCommunications(async (comms) => { + // The first time a block-cacheable request is made, the latest block + // number is retrieved through the block tracker first. It doesn't + // matter what this is — it's just used as a cache key. + comms.mockNextBlockTrackerRequest(); + comms.mockSuccessfulInfuraRpcCall({ + request: requests[0], + response: { result: mockResults[0] }, + }); + + const results = await withInfuraClient(({ makeRpcCallsInSeries }) => + makeRpcCallsInSeries(requests), + ); + + expect(results).toStrictEqual([mockResults[0], mockResults[0]]); + }); + }); + + it('hits Infura and does not reuse the result of a previous request if the latest block number was updated since', async () => { + const requests = [{ method }, { method }]; + const mockResults = [{ blockHash: '0x100' }, { blockHash: '0x200' }]; + + await withMockedInfuraCommunications(async (comms) => { + // Note that we have to mock these requests in a specific order. The + // first block tracker request occurs because of the first RPC + // request. The second block tracker request, however, does not occur + // because of the second RPC request, but rather because we call + // `clock.runAll()` below. + comms.mockNextBlockTrackerRequest({ blockNumber: '0x1' }); + comms.mockSuccessfulInfuraRpcCall({ + request: requests[0], + response: { result: mockResults[0] }, + }); + comms.mockNextBlockTrackerRequest({ blockNumber: '0x2' }); + comms.mockSuccessfulInfuraRpcCall({ + request: requests[1], + response: { result: mockResults[1] }, + }); + + const results = await withInfuraClient(async (client) => { + const firstResult = await client.makeRpcCall(requests[0]); + // Proceed to the next iteration of the block tracker so that a new + // block is fetched and the current block is updated. + client.clock.runAll(); + const secondResult = await client.makeRpcCall(requests[1]); + return [firstResult, secondResult]; + }); + + expect(results).toStrictEqual(mockResults); + }); + }); + + it.each([null, undefined, '\u003cnil\u003e'])( + 'does not reuse the result of a previous request if it was `%s`', + async (emptyValue) => { + const requests = [{ method }, { method }]; + const mockResults = [emptyValue, { blockHash: '0x100' }]; + + await withMockedInfuraCommunications(async (comms) => { + // The first time a block-cacheable request is made, the latest block + // number is retrieved through the block tracker first. It doesn't + // matter what this is — it's just used as a cache key. + comms.mockNextBlockTrackerRequest(); + comms.mockSuccessfulInfuraRpcCall({ + request: requests[0], + response: { result: mockResults[0] }, + }); + comms.mockSuccessfulInfuraRpcCall({ + request: requests[1], + response: { result: mockResults[1] }, + }); + + const results = await withInfuraClient(({ makeRpcCallsInSeries }) => + makeRpcCallsInSeries(requests), + ); + + expect(results).toStrictEqual(mockResults); + }); + }, + ); + + it('does not reuse the result of a previous request if result.blockHash was null', async () => { + const requests = [{ method }, { method }]; + const mockResults = [ + { blockHash: null, extra: 'some value' }, + { blockHash: '0x100', extra: 'some other value' }, + ]; + + await withMockedInfuraCommunications(async (comms) => { + // The first time a block-cacheable request is made, the latest block + // number is retrieved through the block tracker first. It doesn't + // matter what this is — it's just used as a cache key. + comms.mockNextBlockTrackerRequest(); + comms.mockSuccessfulInfuraRpcCall({ + request: requests[0], + response: { result: mockResults[0] }, + }); + comms.mockSuccessfulInfuraRpcCall({ + request: requests[1], + response: { result: mockResults[1] }, + }); + + const results = await withInfuraClient(({ makeRpcCallsInSeries }) => + makeRpcCallsInSeries(requests), + ); + + expect(results).toStrictEqual(mockResults); + }); + }); + + it('does not reuse the result of a previous request if result.blockHash was undefined', async () => { + const requests = [{ method }, { method }]; + const mockResults = [ + { extra: 'some value' }, + { blockHash: '0x100', extra: 'some other value' }, + ]; + + await withMockedInfuraCommunications(async (comms) => { + // The first time a block-cacheable request is made, the latest block + // number is retrieved through the block tracker first. It doesn't + // matter what this is — it's just used as a cache key. + comms.mockNextBlockTrackerRequest(); + comms.mockSuccessfulInfuraRpcCall({ + request: requests[0], + response: { result: mockResults[0] }, + }); + comms.mockSuccessfulInfuraRpcCall({ + request: requests[1], + response: { result: mockResults[1] }, + }); + + const results = await withInfuraClient(({ makeRpcCallsInSeries }) => + makeRpcCallsInSeries(requests), + ); + + expect(results).toStrictEqual(mockResults); + }); + }); + + it('does not reuse the result of a previous request if result.blockHash was "0x0000000000000000000000000000000000000000000000000000000000000000"', async () => { + const requests = [{ method }, { method }]; + const mockResults = [ + { + blockHash: + '0x0000000000000000000000000000000000000000000000000000000000000000', + extra: 'some value', + }, + { blockHash: '0x100', extra: 'some other value' }, + ]; + + await withMockedInfuraCommunications(async (comms) => { + // The first time a block-cacheable request is made, the latest block + // number is retrieved through the block tracker first. It doesn't + // matter what this is — it's just used as a cache key. + comms.mockNextBlockTrackerRequest(); + comms.mockSuccessfulInfuraRpcCall({ + request: requests[0], + response: { result: mockResults[0] }, + }); + comms.mockSuccessfulInfuraRpcCall({ + request: requests[1], + response: { result: mockResults[1] }, + }); + + const results = await withInfuraClient(({ makeRpcCallsInSeries }) => + makeRpcCallsInSeries(requests), + ); + + expect(results).toStrictEqual(mockResults); + }); + }); +} + +/** + * Defines tests which exercise the behavior exhibited by an RPC method that + * takes a block parameter. The value of this parameter can be either a block + * number or a block tag ("latest", "earliest", or "pending") and affects how + * the method is cached. + */ +/* eslint-disable-next-line jest/no-export */ +export function testsForRpcMethodSupportingBlockParam( + method, + { blockParamIndex }, +) { + describe.each([ + ['given no block tag', 'none'], + ['given a block tag of "latest"', 'latest', 'latest'], + ])('%s', (_desc, blockParamType, blockParam) => { + const params = + blockParamType === 'none' + ? buildMockParamsWithoutBlockParamAt(blockParamIndex) + : buildMockParamsWithBlockParamAt(blockParamIndex, blockParam); + + it('does not hit Infura more than once for identical requests', async () => { + const requests = [ + { method, params }, + { method, params }, + ]; + const mockResults = ['first result', 'second result']; + + await withMockedInfuraCommunications(async (comms) => { + // The first time a block-cacheable request is made, the block-cache + // middleware will request the latest block number through the block + // tracker to determine the cache key. Later, the block-ref + // middleware will request the latest block number again to resolve + // the value of "latest", but the block number is cached once made, + // so we only need to mock the request once. + comms.mockNextBlockTrackerRequest({ blockNumber: '0x100' }); + // The block-ref middleware will make the request as specified + // except that the block param is replaced with the latest block + // number. + comms.mockSuccessfulInfuraRpcCall({ + request: buildRequestWithReplacedBlockParam( + requests[0], + blockParamIndex, + '0x100', + ), + response: { result: mockResults[0] }, + }); + // Note that the block-ref middleware will still allow the original + // request to go through. + comms.mockSuccessfulInfuraRpcCall({ + request: requests[0], + response: { result: mockResults[0] }, + }); + + const results = await withInfuraClient(({ makeRpcCallsInSeries }) => + makeRpcCallsInSeries(requests), + ); + + expect(results).toStrictEqual([mockResults[0], mockResults[0]]); + }); + }); + + it('hits Infura and does not reuse the result of a previous request if the latest block number was updated since', async () => { + const requests = [ + { method, params }, + { method, params }, + ]; + const mockResults = ['first result', 'second result']; + + await withMockedInfuraCommunications(async (comms) => { + // Note that we have to mock these requests in a specific order. + // The first block tracker request occurs because of the first RPC + // request. The second block tracker request, however, does not + // occur because of the second RPC request, but rather because we + // call `clock.runAll()` below. + comms.mockNextBlockTrackerRequest({ blockNumber: '0x100' }); + // The block-ref middleware will make the request as specified + // except that the block param is replaced with the latest block + // number. + comms.mockSuccessfulInfuraRpcCall({ + request: buildRequestWithReplacedBlockParam( + requests[0], + blockParamIndex, + '0x100', + ), + response: { result: mockResults[0] }, + }); + // Note that the block-ref middleware will still allow the original + // request to go through. + comms.mockSuccessfulInfuraRpcCall({ + request: requests[0], + response: { result: mockResults[0] }, + }); + comms.mockNextBlockTrackerRequest({ blockNumber: '0x200' }); + comms.mockSuccessfulInfuraRpcCall({ + request: requests[1], + response: { result: mockResults[1] }, + }); + // The previous two requests will happen again, with a different block + // number, in the same order. + comms.mockSuccessfulInfuraRpcCall({ + request: buildRequestWithReplacedBlockParam( + requests[0], + blockParamIndex, + '0x200', + ), + response: { result: mockResults[1] }, + }); + comms.mockSuccessfulInfuraRpcCall({ + request: requests[0], + response: { result: mockResults[1] }, + }); + + const results = await withInfuraClient(async (client) => { + const firstResult = await client.makeRpcCall(requests[0]); + // Proceed to the next iteration of the block tracker so that a + // new block is fetched and the current block is updated. + client.clock.runAll(); + const secondResult = await client.makeRpcCall(requests[1]); + return [firstResult, secondResult]; + }); + + expect(results).toStrictEqual(mockResults); + }); + }); + + it.each([null, undefined, '\u003cnil\u003e'])( + 'does not reuse the result of a previous request if it was `%s`', + async (emptyValue) => { + const requests = [ + { method, params }, + { method, params }, + ]; + const mockResults = [emptyValue, 'some result']; + + await withMockedInfuraCommunications(async (comms) => { + // The first time a block-cacheable request is made, the + // block-cache middleware will request the latest block number + // through the block tracker to determine the cache key. Later, + // the block-ref middleware will request the latest block number + // again to resolve the value of "latest", but the block number is + // cached once made, so we only need to mock the request once. + comms.mockNextBlockTrackerRequest({ blockNumber: '0x100' }); + // The block-ref middleware will make the request as specified + // except that the block param is replaced with the latest block + // number. + comms.mockSuccessfulInfuraRpcCall({ + request: buildRequestWithReplacedBlockParam( + requests[0], + blockParamIndex, + '0x100', + ), + response: { result: mockResults[0] }, + }); + // Note that the block-ref middleware will still allow the original + // request to go through. + comms.mockSuccessfulInfuraRpcCall({ + request: requests[0], + response: { result: mockResults[0] }, + }); + // The previous two requests will happen again, in the same order. + comms.mockSuccessfulInfuraRpcCall({ + request: buildRequestWithReplacedBlockParam( + requests[0], + blockParamIndex, + '0x100', + ), + response: { result: mockResults[1] }, + }); + comms.mockSuccessfulInfuraRpcCall({ + request: requests[0], + response: { result: mockResults[1] }, + }); + + const results = await withInfuraClient(({ makeRpcCallsInSeries }) => + makeRpcCallsInSeries(requests), + ); + + expect(results).toStrictEqual(mockResults); + }); + }, + ); + }); + + describe.each([ + ['given a block tag of "earliest"', 'earliest', 'earliest'], + ['given a block number', 'block number', '0x100'], + ])('%s', (_desc, blockParamType, blockParam) => { + const params = buildMockParamsWithBlockParamAt(blockParamIndex, blockParam); + + it('does not hit Infura more than once for identical requests', async () => { + const requests = [ + { method, params }, + { method, params }, + ]; + const mockResults = ['first result', 'second result']; + + await withMockedInfuraCommunications(async (comms) => { + // The first time a block-cacheable request is made, the block-cache + // middleware will request the latest block number through the block + // tracker to determine the cache key. This block number doesn't + // matter. + comms.mockNextBlockTrackerRequest(); + comms.mockSuccessfulInfuraRpcCall({ + request: requests[0], + response: { result: mockResults[0] }, + }); + + const results = await withInfuraClient(({ makeRpcCallsInSeries }) => + makeRpcCallsInSeries(requests), + ); + + expect(results).toStrictEqual([mockResults[0], mockResults[0]]); + }); + }); + + it('reuses the result of a previous request even if the latest block number was updated since', async () => { + const requests = [ + { method, params }, + { method, params }, + ]; + const mockResults = ['first result', 'second result']; + + await withMockedInfuraCommunications(async (comms) => { + // Note that we have to mock these requests in a specific order. The + // first block tracker request occurs because of the first RPC + // request. The second block tracker request, however, does not + // occur because of the second RPC request, but rather because we + // call `clock.runAll()` below. + comms.mockNextBlockTrackerRequest({ blockNumber: '0x1' }); + comms.mockSuccessfulInfuraRpcCall({ + request: requests[0], + response: { result: mockResults[0] }, + }); + comms.mockNextBlockTrackerRequest({ blockNumber: '0x2' }); + comms.mockSuccessfulInfuraRpcCall({ + request: requests[1], + response: { result: mockResults[1] }, + }); + + const results = await withInfuraClient(async (client) => { + const firstResult = await client.makeRpcCall(requests[0]); + // Proceed to the next iteration of the block tracker so that a + // new block is fetched and the current block is updated. + client.clock.runAll(); + const secondResult = await client.makeRpcCall(requests[1]); + return [firstResult, secondResult]; + }); + + expect(results).toStrictEqual([mockResults[0], mockResults[0]]); + }); + }); + + it.each([null, undefined, '\u003cnil\u003e'])( + 'does not reuse the result of a previous request if it was `%s`', + async (emptyValue) => { + const requests = [ + { method, params }, + { method, params }, + ]; + const mockResults = [emptyValue, 'some result']; + + await withMockedInfuraCommunications(async (comms) => { + // The first time a block-cacheable request is made, the latest block + // number is retrieved through the block tracker first. It doesn't + // matter what this is — it's just used as a cache key. + comms.mockNextBlockTrackerRequest(); + comms.mockSuccessfulInfuraRpcCall({ + request: requests[0], + response: { result: mockResults[0] }, + }); + comms.mockSuccessfulInfuraRpcCall({ + request: requests[1], + response: { result: mockResults[1] }, + }); + + const results = await withInfuraClient(({ makeRpcCallsInSeries }) => + makeRpcCallsInSeries(requests), + ); + + expect(results).toStrictEqual(mockResults); + }); + }, + ); + + if (blockParamType === 'earliest') { + it('treats "0x00" as a synonym for "earliest"', async () => { + const requests = [ + { + method, + params: buildMockParamsWithBlockParamAt( + blockParamIndex, + blockParam, + ), + }, + { + method, + params: buildMockParamsWithBlockParamAt(blockParamIndex, '0x00'), + }, + ]; + const mockResults = ['first result', 'second result']; + + await withMockedInfuraCommunications(async (comms) => { + // The first time a block-cacheable request is made, the latest + // block number is retrieved through the block tracker first. It + // doesn't matter what this is — it's just used as a cache key. + comms.mockNextBlockTrackerRequest(); + comms.mockSuccessfulInfuraRpcCall({ + request: requests[0], + response: { result: mockResults[0] }, + }); + + const results = await withInfuraClient(({ makeRpcCallsInSeries }) => + makeRpcCallsInSeries(requests), + ); + + expect(results).toStrictEqual([mockResults[0], mockResults[0]]); + }); + }); + } + + if (blockParamType === 'block number') { + it('does not reuse the result of a previous request if it was made with different arguments than this one', async () => { + await withMockedInfuraCommunications(async (comms) => { + const requests = [ + { + method, + params: buildMockParamsWithBlockParamAt(blockParamIndex, '0x100'), + }, + { + method, + params: buildMockParamsWithBlockParamAt(blockParamIndex, '0x200'), + }, + ]; + + // The first time a block-cacheable request is made, the latest block + // number is retrieved through the block tracker first. It doesn't + // matter what this is — it's just used as a cache key. + comms.mockNextBlockTrackerRequest(); + comms.mockSuccessfulInfuraRpcCall({ + request: requests[0], + response: { result: 'first result' }, + }); + comms.mockSuccessfulInfuraRpcCall({ + request: requests[1], + response: { result: 'second result' }, + }); + + const results = await withInfuraClient(({ makeRpcCallsInSeries }) => + makeRpcCallsInSeries(requests), + ); + + expect(results).toStrictEqual(['first result', 'second result']); + }); + }); + } + }); + + describe('given a block tag of "pending"', () => { + const params = buildMockParamsWithBlockParamAt(blockParamIndex, 'pending'); + + it('hits Infura on all calls and does not cache anything', async () => { + const requests = [ + { method, params }, + { method, params }, + ]; + const mockResults = ['first result', 'second result']; + + await withMockedInfuraCommunications(async (comms) => { + // The first time a block-cacheable request is made, the latest + // block number is retrieved through the block tracker first. It + // doesn't matter what this is — it's just used as a cache key. + comms.mockNextBlockTrackerRequest(); + comms.mockSuccessfulInfuraRpcCall({ + request: requests[0], + response: { result: mockResults[0] }, + }); + comms.mockSuccessfulInfuraRpcCall({ + request: requests[1], + response: { result: mockResults[1] }, + }); + + const results = await withInfuraClient(({ makeRpcCallsInSeries }) => + makeRpcCallsInSeries(requests), + ); + + expect(results).toStrictEqual(mockResults); + }); + }); + }); +} diff --git a/lavamoat/browserify/beta/policy.json b/lavamoat/browserify/beta/policy.json index 96fc18248..b9fa80382 100644 --- a/lavamoat/browserify/beta/policy.json +++ b/lavamoat/browserify/beta/policy.json @@ -3024,7 +3024,7 @@ }, "packages": { "@metamask/eth-json-rpc-infura>@metamask/utils": true, - "eth-json-rpc-middleware": true, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": true, "eth-rpc-errors": true, "json-rpc-engine": true, "node-fetch": true @@ -3037,6 +3037,70 @@ "madge>debug": true } }, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": { + "globals": { + "URL": true, + "btoa": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>eth-sig-util": true, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>pify": true, + "browserify>browser-resolve": true, + "eth-rpc-errors": true, + "json-rpc-engine": true, + "json-rpc-engine>@metamask/safe-event-emitter": true, + "lavamoat>json-stable-stringify": true, + "vinyl>clone": true + } + }, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>eth-sig-util": { + "packages": { + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>eth-sig-util>ethereumjs-abi": true, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>eth-sig-util>ethereumjs-util": true + } + }, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>eth-sig-util>ethereumjs-abi": { + "packages": { + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>eth-sig-util>ethereumjs-abi>ethereumjs-util": true, + "bn.js": true, + "browserify>buffer": true + } + }, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>eth-sig-util>ethereumjs-abi>ethereumjs-util": { + "packages": { + "3box>ethers>elliptic": true, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>eth-sig-util>ethereumjs-util>ethjs-util": true, + "bn.js": true, + "browserify>assert": true, + "browserify>buffer": true, + "ethereumjs-util>create-hash": true, + "ethereumjs-util>ethereum-cryptography": true, + "ethereumjs-util>rlp": true + } + }, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>eth-sig-util>ethereumjs-util": { + "packages": { + "3box>ethers>elliptic": true, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>eth-sig-util>ethereumjs-util>ethjs-util": true, + "bn.js": true, + "browserify>assert": true, + "browserify>buffer": true, + "ethereumjs-util>create-hash": true, + "ethereumjs-util>ethereum-cryptography": true, + "ethereumjs-util>rlp": true, + "ethereumjs-wallet>safe-buffer": true + } + }, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>eth-sig-util>ethereumjs-util>ethjs-util": { + "packages": { + "browserify>buffer": true, + "ethjs>ethjs-util>is-hex-prefixed": true, + "ethjs>ethjs-util>strip-hex-prefix": true + } + }, "@metamask/eth-ledger-bridge-keyring": { "globals": { "addEventListener": true, @@ -4295,11 +4359,19 @@ "setTimeout": true }, "packages": { + "eth-block-tracker>@metamask/utils": true, "eth-block-tracker>pify": true, "eth-query>json-rpc-random-id": true, "json-rpc-engine>@metamask/safe-event-emitter": true } }, + "eth-block-tracker>@metamask/utils": { + "packages": { + "eslint>fast-deep-equal": true, + "eth-block-tracker>@metamask/utils>superstruct": true, + "madge>debug": true + } + }, "eth-ens-namehash": { "globals": { "name": "write" @@ -4353,6 +4425,7 @@ }, "packages": { "browserify>browser-resolve": true, + "eth-json-rpc-middleware>@metamask/utils": true, "eth-json-rpc-middleware>eth-sig-util": true, "eth-json-rpc-middleware>pify": true, "eth-rpc-errors": true, @@ -4362,6 +4435,13 @@ "vinyl>clone": true } }, + "eth-json-rpc-middleware>@metamask/utils": { + "packages": { + "eslint>fast-deep-equal": true, + "eth-json-rpc-middleware>@metamask/utils>superstruct": true, + "madge>debug": true + } + }, "eth-json-rpc-middleware>eth-sig-util": { "packages": { "eth-json-rpc-middleware>eth-sig-util>ethereumjs-abi": true, @@ -4668,6 +4748,7 @@ "eth-query": { "packages": { "eth-query>json-rpc-random-id": true, + "madge>debug": true, "watchify>xtend": true } }, diff --git a/lavamoat/browserify/flask/policy.json b/lavamoat/browserify/flask/policy.json index eb19c56fc..ecfe34552 100644 --- a/lavamoat/browserify/flask/policy.json +++ b/lavamoat/browserify/flask/policy.json @@ -3169,7 +3169,7 @@ }, "packages": { "@metamask/eth-json-rpc-infura>@metamask/utils": true, - "eth-json-rpc-middleware": true, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": true, "eth-rpc-errors": true, "json-rpc-engine": true, "node-fetch": true @@ -3182,6 +3182,70 @@ "madge>debug": true } }, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": { + "globals": { + "URL": true, + "btoa": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>eth-sig-util": true, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>pify": true, + "browserify>browser-resolve": true, + "eth-rpc-errors": true, + "json-rpc-engine": true, + "json-rpc-engine>@metamask/safe-event-emitter": true, + "lavamoat>json-stable-stringify": true, + "vinyl>clone": true + } + }, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>eth-sig-util": { + "packages": { + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>eth-sig-util>ethereumjs-abi": true, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>eth-sig-util>ethereumjs-util": true + } + }, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>eth-sig-util>ethereumjs-abi": { + "packages": { + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>eth-sig-util>ethereumjs-abi>ethereumjs-util": true, + "bn.js": true, + "browserify>buffer": true + } + }, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>eth-sig-util>ethereumjs-abi>ethereumjs-util": { + "packages": { + "3box>ethers>elliptic": true, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>eth-sig-util>ethereumjs-util>ethjs-util": true, + "bn.js": true, + "browserify>assert": true, + "browserify>buffer": true, + "ethereumjs-util>create-hash": true, + "ethereumjs-util>ethereum-cryptography": true, + "ethereumjs-util>rlp": true + } + }, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>eth-sig-util>ethereumjs-util": { + "packages": { + "3box>ethers>elliptic": true, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>eth-sig-util>ethereumjs-util>ethjs-util": true, + "bn.js": true, + "browserify>assert": true, + "browserify>buffer": true, + "ethereumjs-util>create-hash": true, + "ethereumjs-util>ethereum-cryptography": true, + "ethereumjs-util>rlp": true, + "ethereumjs-wallet>safe-buffer": true + } + }, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>eth-sig-util>ethereumjs-util>ethjs-util": { + "packages": { + "browserify>buffer": true, + "ethjs>ethjs-util>is-hex-prefixed": true, + "ethjs>ethjs-util>strip-hex-prefix": true + } + }, "@metamask/eth-ledger-bridge-keyring": { "globals": { "addEventListener": true, @@ -4837,11 +4901,19 @@ "setTimeout": true }, "packages": { + "eth-block-tracker>@metamask/utils": true, "eth-block-tracker>pify": true, "eth-query>json-rpc-random-id": true, "json-rpc-engine>@metamask/safe-event-emitter": true } }, + "eth-block-tracker>@metamask/utils": { + "packages": { + "eslint>fast-deep-equal": true, + "eth-block-tracker>@metamask/utils>superstruct": true, + "madge>debug": true + } + }, "eth-ens-namehash": { "globals": { "name": "write" @@ -4895,6 +4967,7 @@ }, "packages": { "browserify>browser-resolve": true, + "eth-json-rpc-middleware>@metamask/utils": true, "eth-json-rpc-middleware>eth-sig-util": true, "eth-json-rpc-middleware>pify": true, "eth-rpc-errors": true, @@ -4904,6 +4977,13 @@ "vinyl>clone": true } }, + "eth-json-rpc-middleware>@metamask/utils": { + "packages": { + "eslint>fast-deep-equal": true, + "eth-json-rpc-middleware>@metamask/utils>superstruct": true, + "madge>debug": true + } + }, "eth-json-rpc-middleware>eth-sig-util": { "packages": { "eth-json-rpc-middleware>eth-sig-util>ethereumjs-abi": true, @@ -5210,6 +5290,7 @@ "eth-query": { "packages": { "eth-query>json-rpc-random-id": true, + "madge>debug": true, "watchify>xtend": true } }, diff --git a/lavamoat/browserify/main/policy.json b/lavamoat/browserify/main/policy.json index 96fc18248..b9fa80382 100644 --- a/lavamoat/browserify/main/policy.json +++ b/lavamoat/browserify/main/policy.json @@ -3024,7 +3024,7 @@ }, "packages": { "@metamask/eth-json-rpc-infura>@metamask/utils": true, - "eth-json-rpc-middleware": true, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": true, "eth-rpc-errors": true, "json-rpc-engine": true, "node-fetch": true @@ -3037,6 +3037,70 @@ "madge>debug": true } }, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": { + "globals": { + "URL": true, + "btoa": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>eth-sig-util": true, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>pify": true, + "browserify>browser-resolve": true, + "eth-rpc-errors": true, + "json-rpc-engine": true, + "json-rpc-engine>@metamask/safe-event-emitter": true, + "lavamoat>json-stable-stringify": true, + "vinyl>clone": true + } + }, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>eth-sig-util": { + "packages": { + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>eth-sig-util>ethereumjs-abi": true, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>eth-sig-util>ethereumjs-util": true + } + }, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>eth-sig-util>ethereumjs-abi": { + "packages": { + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>eth-sig-util>ethereumjs-abi>ethereumjs-util": true, + "bn.js": true, + "browserify>buffer": true + } + }, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>eth-sig-util>ethereumjs-abi>ethereumjs-util": { + "packages": { + "3box>ethers>elliptic": true, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>eth-sig-util>ethereumjs-util>ethjs-util": true, + "bn.js": true, + "browserify>assert": true, + "browserify>buffer": true, + "ethereumjs-util>create-hash": true, + "ethereumjs-util>ethereum-cryptography": true, + "ethereumjs-util>rlp": true + } + }, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>eth-sig-util>ethereumjs-util": { + "packages": { + "3box>ethers>elliptic": true, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>eth-sig-util>ethereumjs-util>ethjs-util": true, + "bn.js": true, + "browserify>assert": true, + "browserify>buffer": true, + "ethereumjs-util>create-hash": true, + "ethereumjs-util>ethereum-cryptography": true, + "ethereumjs-util>rlp": true, + "ethereumjs-wallet>safe-buffer": true + } + }, + "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>eth-sig-util>ethereumjs-util>ethjs-util": { + "packages": { + "browserify>buffer": true, + "ethjs>ethjs-util>is-hex-prefixed": true, + "ethjs>ethjs-util>strip-hex-prefix": true + } + }, "@metamask/eth-ledger-bridge-keyring": { "globals": { "addEventListener": true, @@ -4295,11 +4359,19 @@ "setTimeout": true }, "packages": { + "eth-block-tracker>@metamask/utils": true, "eth-block-tracker>pify": true, "eth-query>json-rpc-random-id": true, "json-rpc-engine>@metamask/safe-event-emitter": true } }, + "eth-block-tracker>@metamask/utils": { + "packages": { + "eslint>fast-deep-equal": true, + "eth-block-tracker>@metamask/utils>superstruct": true, + "madge>debug": true + } + }, "eth-ens-namehash": { "globals": { "name": "write" @@ -4353,6 +4425,7 @@ }, "packages": { "browserify>browser-resolve": true, + "eth-json-rpc-middleware>@metamask/utils": true, "eth-json-rpc-middleware>eth-sig-util": true, "eth-json-rpc-middleware>pify": true, "eth-rpc-errors": true, @@ -4362,6 +4435,13 @@ "vinyl>clone": true } }, + "eth-json-rpc-middleware>@metamask/utils": { + "packages": { + "eslint>fast-deep-equal": true, + "eth-json-rpc-middleware>@metamask/utils>superstruct": true, + "madge>debug": true + } + }, "eth-json-rpc-middleware>eth-sig-util": { "packages": { "eth-json-rpc-middleware>eth-sig-util>ethereumjs-abi": true, @@ -4668,6 +4748,7 @@ "eth-query": { "packages": { "eth-query>json-rpc-random-id": true, + "madge>debug": true, "watchify>xtend": true } }, diff --git a/package.json b/package.json index a61a7b5f5..99440f127 100644 --- a/package.json +++ b/package.json @@ -162,10 +162,10 @@ "debounce-stream": "^2.0.0", "deep-freeze-strict": "1.1.1", "end-of-stream": "^1.4.4", - "eth-block-tracker": "^5.0.1", + "eth-block-tracker": "^6.0.0", "eth-ens-namehash": "^2.0.8", "eth-json-rpc-filters": "^4.2.1", - "eth-json-rpc-middleware": "^8.0.0", + "eth-json-rpc-middleware": "^9.0.0", "eth-keyring-controller": "^7.0.2", "eth-lattice-keyring": "^0.12.0", "eth-method-registry": "^2.0.0", @@ -444,8 +444,6 @@ "@storybook/core>@storybook/core-client>@storybook/ui>core-js-pure": false, "eth-json-rpc-filters>eth-json-rpc-middleware>ethereumjs-util>keccak": false, "eth-json-rpc-filters>eth-json-rpc-middleware>ethereumjs-util>secp256k1": false, - "eth-json-rpc-infura>eth-json-rpc-middleware>ethereumjs-util>keccak": false, - "eth-json-rpc-infura>eth-json-rpc-middleware>ethereumjs-util>secp256k1": false, "eth-json-rpc-middleware>eth-sig-util>ethereumjs-util>keccak": false, "eth-json-rpc-middleware>eth-sig-util>ethereumjs-util>secp256k1": false, "eth-lattice-keyring>gridplus-sdk": false, diff --git a/patches/eth-query+2.1.2.patch b/patches/eth-query+2.1.2.patch new file mode 100644 index 000000000..ea405d5f8 --- /dev/null +++ b/patches/eth-query+2.1.2.patch @@ -0,0 +1,22 @@ +diff --git a/node_modules/eth-query/index.js b/node_modules/eth-query/index.js +index 13e9f3c..303d703 100644 +--- a/node_modules/eth-query/index.js ++++ b/node_modules/eth-query/index.js +@@ -1,5 +1,6 @@ + const extend = require('xtend') + const createRandomId = require('json-rpc-random-id')() ++const debug = require('debug')('eth-query'); + + module.exports = EthQuery + +@@ -63,7 +64,9 @@ EthQuery.prototype.submitHashrate = generateFnFor('eth_subm + + EthQuery.prototype.sendAsync = function(opts, cb){ + const self = this +- self.currentProvider.sendAsync(createPayload(opts), function(err, response){ ++ const payload = createPayload(opts) ++ debug('making request %o', payload) ++ self.currentProvider.sendAsync(payload, function(err, response){ + if (!err && response.error) err = new Error('EthQuery - RPC Error - '+response.error.message) + if (err) return cb(err) + cb(null, response.result) diff --git a/yarn.lock b/yarn.lock index 12b73dff7..25bc4bbd3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11708,6 +11708,16 @@ eth-block-tracker@^5.0.1: json-rpc-random-id "^1.0.1" pify "^3.0.0" +eth-block-tracker@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/eth-block-tracker/-/eth-block-tracker-6.0.0.tgz#24d9d730f16d1f39fd69b67109fb09d9d3d8e8fb" + integrity sha512-dkrflt13vxcbXUCzwqBxe4faahWFDGccVPAHgN+9fd1FMu9l+fvMYaNPPVO9LaIUy0zQ7NLKeWiDnsQl/vadbg== + dependencies: + "@metamask/safe-event-emitter" "^2.0.0" + "@metamask/utils" "^3.0.1" + json-rpc-random-id "^1.0.1" + pify "^3.0.0" + eth-eip712-util-browser@^0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/eth-eip712-util-browser/-/eth-eip712-util-browser-0.0.3.tgz#334143d76d0a502b456e2ee5f1ce20f678a39fd3" @@ -11764,7 +11774,7 @@ eth-json-rpc-middleware@^6.0.0: pify "^3.0.0" safe-event-emitter "^1.0.1" -eth-json-rpc-middleware@^8.0.0, eth-json-rpc-middleware@^8.1.0: +eth-json-rpc-middleware@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/eth-json-rpc-middleware/-/eth-json-rpc-middleware-8.1.0.tgz#d2bf980768b43dcbcb7830ed1fcc34a16f63074d" integrity sha512-0ZZDr6KoddqN4N100a7YWzLTYrVN0P49DJ3Su1vsRavPpieq92rgZymnQcr0tLFBQDEJg1MVqLex46fA0GEkDA== @@ -11780,6 +11790,23 @@ eth-json-rpc-middleware@^8.0.0, eth-json-rpc-middleware@^8.1.0: node-fetch "^2.6.7" pify "^3.0.0" +eth-json-rpc-middleware@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/eth-json-rpc-middleware/-/eth-json-rpc-middleware-9.0.0.tgz#8858713fa009e58758c34f7d8cdeb6179428ffd9" + integrity sha512-s/3BFGTjnby2CXAmWPXdNdWwmlygvRPsKRMYA3a5TJKCH8F/lVmVwGtckYRgfY3etpsEO8rMbkp6SQ+Ob1zCiQ== + dependencies: + "@metamask/safe-event-emitter" "^2.0.0" + "@metamask/utils" "^3.0.1" + btoa "^1.2.1" + clone "^2.1.1" + eth-block-tracker "^5.0.1" + eth-rpc-errors "^4.0.3" + eth-sig-util "^1.4.2" + json-rpc-engine "^6.1.0" + json-stable-stringify "^1.0.1" + node-fetch "^2.6.7" + pify "^3.0.0" + eth-keyring-controller@^7.0.2: version "7.0.2" resolved "https://registry.yarnpkg.com/eth-keyring-controller/-/eth-keyring-controller-7.0.2.tgz#c4d7f9be179f08b3bb18410066bc4c8e91f50552"