From 89cec5335f8ba02f4a976fdde663db8211c7109b Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Thu, 22 Jun 2023 11:46:09 -0700 Subject: [PATCH] Replace NetworkController w/ core version (#19486) This commit fulfills a long-standing desire to get the extension using the same network controller as mobile by removing NetworkController from this repo and replacing it with NetworkController from the `@metamask/network-controller` package. The new version of NetworkController is different the old one in a few ways: - The new controller inherits from BaseControllerV2, so the `state` property is used to access the state instead of `store.getState()`. All references of the latter have been replaced with the former. - As the new controller no longer has a `store` property, it cannot be subscribed to; the controller takes a messenger which can be subscribed to instead. There were various places within MetamaskController where the old way of subscribing has been replaced with the new way. In addition, DetectTokensController has been updated to take a messenger object so that it can listen for NetworkController state changes. - The state of the new controller is not updatable from the outside. This affected BackupController, which dumps state from NetworkController (among other controllers), but also loads the same state into NetworkController on import. A method `loadBackup` has been added to NetworkController to facilitate this use case, and BackupController is now using this method instead of attempting to call `update` on NetworkController. - The new controller does not have a `getCurrentChainId` method; instead, the chain ID can be read from the provider config in state. This affected MmiController. (MmiController was also updated to read custom networks from the new network controller instead of the preferences controller). - The default network that the new controller is set to is always Mainnet (previously it could be either localhost or Goerli in test mode, depending on environment variables). This has been addressed by feeding the NetworkController initial state using the old logic, so this should not apply. --- .eslintrc.js | 4 - .mocharc.js | 1 - app/scripts/background.js | 2 +- app/scripts/controllers/backup.js | 4 +- app/scripts/controllers/backup.test.js | 21 +- app/scripts/controllers/detect-tokens.js | 35 +- app/scripts/controllers/detect-tokens.test.js | 38 +- app/scripts/controllers/metametrics.test.js | 61 +- app/scripts/controllers/mmi-controller.js | 25 +- .../network/create-network-client.test.ts | 7 - .../network/create-network-client.ts | 191 - app/scripts/controllers/network/index.ts | 1 - .../network/network-controller.test.ts | 7676 ----------------- .../controllers/network/network-controller.ts | 1196 --- .../block-hash-in-response.ts | 284 - .../network/provider-api-tests/block-param.ts | 2070 ----- .../network/provider-api-tests/helpers.ts | 543 -- .../provider-api-tests/no-block-param.ts | 977 --- .../not-handled-by-middleware.ts | 63 - .../provider-api-tests/shared-tests.ts | 372 - .../network/test/fake-block-tracker.ts | 11 - .../controllers/network/test/fake-provider.ts | 206 - app/scripts/controllers/swaps.js | 4 +- app/scripts/controllers/swaps.test.js | 44 +- app/scripts/metamask-controller.js | 194 +- jest.config.js | 5 - lavamoat/browserify/beta/policy.json | 165 +- lavamoat/browserify/desktop/policy.json | 165 +- lavamoat/browserify/flask/policy.json | 165 +- lavamoat/browserify/main/policy.json | 165 +- lavamoat/browserify/mmi/policy.json | 165 +- package.json | 8 +- yarn.lock | 35 +- 33 files changed, 688 insertions(+), 14215 deletions(-) delete mode 100644 app/scripts/controllers/network/create-network-client.test.ts delete mode 100644 app/scripts/controllers/network/create-network-client.ts delete mode 100644 app/scripts/controllers/network/index.ts delete mode 100644 app/scripts/controllers/network/network-controller.test.ts delete mode 100644 app/scripts/controllers/network/network-controller.ts delete mode 100644 app/scripts/controllers/network/provider-api-tests/block-hash-in-response.ts delete mode 100644 app/scripts/controllers/network/provider-api-tests/block-param.ts delete mode 100644 app/scripts/controllers/network/provider-api-tests/helpers.ts delete mode 100644 app/scripts/controllers/network/provider-api-tests/no-block-param.ts delete mode 100644 app/scripts/controllers/network/provider-api-tests/not-handled-by-middleware.ts delete mode 100644 app/scripts/controllers/network/provider-api-tests/shared-tests.ts delete mode 100644 app/scripts/controllers/network/test/fake-block-tracker.ts delete mode 100644 app/scripts/controllers/network/test/fake-provider.ts diff --git a/.eslintrc.js b/.eslintrc.js index 1236df167..4ef8de9bf 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -238,7 +238,6 @@ module.exports = { excludedFiles: [ 'app/scripts/controllers/app-state.test.js', 'app/scripts/controllers/mmi-controller.test.js', - 'app/scripts/controllers/network/**/*.test.js', 'app/scripts/controllers/permissions/**/*.test.js', 'app/scripts/lib/**/*.test.js', 'app/scripts/migrations/*.test.js', @@ -267,9 +266,6 @@ module.exports = { '**/__snapshots__/*.snap', 'app/scripts/controllers/app-state.test.js', 'app/scripts/controllers/mmi-controller.test.js', - 'app/scripts/controllers/network/**/*.test.js', - 'app/scripts/controllers/network/**/*.test.ts', - 'app/scripts/controllers/network/provider-api-tests/*.ts', 'app/scripts/controllers/permissions/**/*.test.js', 'app/scripts/lib/**/*.test.js', 'app/scripts/migrations/*.test.js', diff --git a/.mocharc.js b/.mocharc.js index a8843b0e1..2ed699e6b 100644 --- a/.mocharc.js +++ b/.mocharc.js @@ -6,7 +6,6 @@ module.exports = { './app/scripts/migrations/*.test.js', './app/scripts/platforms/*.test.js', './app/scripts/controllers/app-state.test.js', - './app/scripts/controllers/network/**/*.test.js', './app/scripts/controllers/permissions/**/*.test.js', './app/scripts/controllers/mmi-controller.test.js', './app/scripts/constants/error-utils.test.js', diff --git a/app/scripts/background.js b/app/scripts/background.js index bd80b71ac..e3416b796 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -462,7 +462,7 @@ export function setupController( setupEnsIpfsResolver({ getCurrentChainId: () => - controller.networkController.store.getState().providerConfig.chainId, + controller.networkController.state.providerConfig.chainId, getIpfsGateway: controller.preferencesController.getIpfsGateway.bind( controller.preferencesController, ), diff --git a/app/scripts/controllers/backup.js b/app/scripts/controllers/backup.js index 5873bc08c..159f02d2d 100644 --- a/app/scripts/controllers/backup.js +++ b/app/scripts/controllers/backup.js @@ -31,7 +31,7 @@ export default class BackupController { } if (network) { - this.networkController.store.updateState(network); + this.networkController.loadBackup(network); } if (preferences || addressBook || network) { @@ -48,7 +48,7 @@ export default class BackupController { addressBook: { ...this.addressBookController.state }, network: { networkConfigurations: - this.networkController.store.getState().networkConfigurations, + this.networkController.state.networkConfigurations, }, }; diff --git a/app/scripts/controllers/backup.test.js b/app/scripts/controllers/backup.test.js index fdeba7faf..25f153a37 100644 --- a/app/scripts/controllers/backup.test.js +++ b/app/scripts/controllers/backup.test.js @@ -57,18 +57,15 @@ function getMockAddressBookController() { } function getMockNetworkController() { - const mcState = { + const state = { networkConfigurations: {}, - - update: (store) => (mcState.store = store), }; - mcState.store = { - getState: sinon.stub().returns(mcState), - updateState: (store) => (mcState.store = store), + const loadBackup = ({ networkConfigurations }) => { + Object.assign(state, { networkConfigurations }); }; - return mcState; + return { state, loadBackup }; } const jsonData = JSON.stringify({ @@ -174,28 +171,28 @@ describe('BackupController', function () { it('should restore backup', async function () { const backupController = getBackupController(); - backupController.restoreUserData(jsonData); + await backupController.restoreUserData(jsonData); // check networks backup assert.equal( - backupController.networkController.store.networkConfigurations[ + backupController.networkController.state.networkConfigurations[ 'network-configuration-id-1' ].chainId, '0x539', ); assert.equal( - backupController.networkController.store.networkConfigurations[ + backupController.networkController.state.networkConfigurations[ 'network-configuration-id-2' ].chainId, '0x38', ); assert.equal( - backupController.networkController.store.networkConfigurations[ + backupController.networkController.state.networkConfigurations[ 'network-configuration-id-3' ].chainId, '0x61', ); assert.equal( - backupController.networkController.store.networkConfigurations[ + backupController.networkController.state.networkConfigurations[ 'network-configuration-id-4' ].chainId, '0x89', diff --git a/app/scripts/controllers/detect-tokens.js b/app/scripts/controllers/detect-tokens.js index 33e765f91..46719aebc 100644 --- a/app/scripts/controllers/detect-tokens.js +++ b/app/scripts/controllers/detect-tokens.js @@ -33,8 +33,10 @@ export default class DetectTokensController { * @param config.tokensController * @param config.assetsContractController * @param config.trackMetaMetricsEvent + * @param config.messenger */ constructor({ + messenger, interval = DEFAULT_INTERVAL, preferences, network, @@ -44,6 +46,7 @@ export default class DetectTokensController { assetsContractController = null, trackMetaMetricsEvent, } = {}) { + this.messenger = messenger; this.assetsContractController = assetsContractController; this.tokensController = tokensController; this.preferences = preferences; @@ -59,7 +62,7 @@ export default class DetectTokensController { }); this.hiddenTokens = this.tokensController?.state.ignoredTokens; this.detectedTokens = this.tokensController?.state.detectedTokens; - this.chainId = this.getChainIdFromNetworkStore(network); + this.chainId = this.getChainIdFromNetworkStore(); this._trackMetaMetricsEvent = trackMetaMetricsEvent; preferences?.store.subscribe(({ selectedAddress, useTokenDetection }) => { @@ -81,6 +84,13 @@ export default class DetectTokensController { this.detectedTokens = detectedTokens; }, ); + messenger.subscribe('NetworkController:stateChange', () => { + if (this.chainId !== this.getChainIdFromNetworkStore()) { + const chainId = this.getChainIdFromNetworkStore(); + this.chainId = chainId; + this.restartTokenDetection({ chainId: this.chainId }); + } + }); } /** @@ -93,7 +103,7 @@ export default class DetectTokensController { async detectNewTokens({ selectedAddress, chainId } = {}) { const addressAgainstWhichToDetect = selectedAddress ?? this.selectedAddress; const chainIdAgainstWhichToDetect = - chainId ?? this.getChainIdFromNetworkStore(this._network); + chainId ?? this.getChainIdFromNetworkStore(); if (!this.isActive) { return; } @@ -208,8 +218,8 @@ export default class DetectTokensController { this.interval = DEFAULT_INTERVAL; } - getChainIdFromNetworkStore(network) { - return network?.store.getState().providerConfig.chainId; + getChainIdFromNetworkStore() { + return this.network?.state.providerConfig.chainId; } /* eslint-disable accessor-pairs */ @@ -226,23 +236,6 @@ export default class DetectTokensController { }, interval); } - /** - * @type {object} - */ - set network(network) { - if (!network) { - return; - } - this._network = network; - this._network.store.subscribe(() => { - if (this.chainId !== this.getChainIdFromNetworkStore(network)) { - const chainId = this.getChainIdFromNetworkStore(network); - this.chainId = chainId; - this.restartTokenDetection({ chainId: this.chainId }); - } - }); - } - /** * In setter when isUnlocked is updated to true, detectNewTokens and restart polling * diff --git a/app/scripts/controllers/detect-tokens.test.js b/app/scripts/controllers/detect-tokens.test.js index a1fee90ce..beb0e9913 100644 --- a/app/scripts/controllers/detect-tokens.test.js +++ b/app/scripts/controllers/detect-tokens.test.js @@ -10,12 +10,19 @@ import { AssetsContractController, } from '@metamask/assets-controllers'; import { toHex } from '@metamask/controller-utils'; +import { NetworkController } from '@metamask/network-controller'; import { NETWORK_TYPES } from '../../../shared/constants/network'; import { toChecksumHexAddress } from '../../../shared/modules/hexstring-utils'; import DetectTokensController from './detect-tokens'; -import { NetworkController } from './network'; import PreferencesController from './preferences'; +function buildMessenger() { + return new ControllerMessenger().getRestricted({ + name: 'DetectTokensController', + allowedEvents: ['NetworkController:stateChange'], + }); +} + describe('DetectTokensController', function () { let sandbox, assetsContractController, @@ -230,23 +237,20 @@ describe('DetectTokensController', function () { onPreferencesStateChange: preferences.store.subscribe.bind( preferences.store, ), - onNetworkStateChange: (cb) => - network.store.subscribe((networkState) => { - const modifiedNetworkState = { - ...networkState, - providerConfig: { - ...networkState.providerConfig, - }, - }; - return cb(modifiedNetworkState); - }), + onNetworkStateChange: networkControllerMessenger.subscribe.bind( + networkControllerMessenger, + 'NetworkController:stateChange', + ), }); assetsContractController = new AssetsContractController({ onPreferencesStateChange: preferences.store.subscribe.bind( preferences.store, ), - onNetworkStateChange: network.store.subscribe.bind(network.store), + onNetworkStateChange: networkControllerMessenger.subscribe.bind( + networkControllerMessenger, + 'NetworkController:stateChange', + ), }); }); @@ -257,7 +261,7 @@ describe('DetectTokensController', function () { it('should poll on correct interval', async function () { const stub = sinon.stub(global, 'setInterval'); - new DetectTokensController({ interval: 1337 }); // eslint-disable-line no-new + new DetectTokensController({ messenger: buildMessenger(), interval: 1337 }); // eslint-disable-line no-new assert.strictEqual(stub.getCall(0).args[1], 1337); stub.restore(); }); @@ -266,6 +270,7 @@ describe('DetectTokensController', function () { const clock = sandbox.useFakeTimers(); await network.setProviderType(NETWORK_TYPES.MAINNET); const controller = new DetectTokensController({ + messenger: buildMessenger(), preferences, network, keyringMemStore, @@ -302,6 +307,7 @@ describe('DetectTokensController', function () { }); await tokenListController.start(); const controller = new DetectTokensController({ + messenger: buildMessenger(), preferences, network, keyringMemStore, @@ -325,6 +331,7 @@ describe('DetectTokensController', function () { sandbox.useFakeTimers(); await network.setProviderType(NETWORK_TYPES.MAINNET); const controller = new DetectTokensController({ + messenger: buildMessenger(), preferences, network, keyringMemStore, @@ -376,6 +383,7 @@ describe('DetectTokensController', function () { sandbox.useFakeTimers(); await network.setProviderType(NETWORK_TYPES.MAINNET); const controller = new DetectTokensController({ + messenger: buildMessenger(), preferences, network, keyringMemStore, @@ -434,6 +442,7 @@ describe('DetectTokensController', function () { it('should trigger detect new tokens when change address', async function () { sandbox.useFakeTimers(); const controller = new DetectTokensController({ + messenger: buildMessenger(), preferences, network, keyringMemStore, @@ -453,6 +462,7 @@ describe('DetectTokensController', function () { it('should trigger detect new tokens when submit password', async function () { sandbox.useFakeTimers(); const controller = new DetectTokensController({ + messenger: buildMessenger(), preferences, network, keyringMemStore, @@ -471,6 +481,7 @@ describe('DetectTokensController', function () { const clock = sandbox.useFakeTimers(); await network.setProviderType(NETWORK_TYPES.MAINNET); const controller = new DetectTokensController({ + messenger: buildMessenger(), preferences, network, keyringMemStore, @@ -492,6 +503,7 @@ describe('DetectTokensController', function () { const clock = sandbox.useFakeTimers(); await network.setProviderType(NETWORK_TYPES.MAINNET); const controller = new DetectTokensController({ + messenger: buildMessenger(), preferences, network, keyringMemStore, diff --git a/app/scripts/controllers/metametrics.test.js b/app/scripts/controllers/metametrics.test.js index 780da42b4..e881f4772 100644 --- a/app/scripts/controllers/metametrics.test.js +++ b/app/scripts/controllers/metametrics.test.js @@ -9,11 +9,7 @@ import { MetaMetricsUserTrait, } from '../../../shared/constants/metametrics'; import waitUntilCalled from '../../../test/lib/wait-until-called'; -import { - CHAIN_IDS, - CURRENCY_SYMBOLS, - NETWORK_TYPES, -} from '../../../shared/constants/network'; +import { CHAIN_IDS, CURRENCY_SYMBOLS } from '../../../shared/constants/network'; import * as Utils from '../lib/util'; import MetaMetricsController from './metametrics'; @@ -77,28 +73,6 @@ const DEFAULT_PAGE_PROPERTIES = { ...DEFAULT_SHARED_PROPERTIES, }; -function getMockNetworkController() { - let state = { - providerConfig: { - type: NETWORK_TYPES.GOERLI, - chainId: FAKE_CHAIN_ID, - }, - network: 'loading', - }; - const onNetworkDidChange = sinon.stub(); - const updateState = (newState) => { - state = { ...state, ...newState }; - onNetworkDidChange.getCall(0).args[0](); - }; - return { - store: { - getState: () => state, - updateState, - }, - onNetworkDidChange, - }; -} - function getMockPreferencesStore({ currentLocale = LOCALE } = {}) { let preferencesStore = { currentLocale, @@ -142,15 +116,16 @@ function getMetaMetricsController({ participateInMetaMetrics = true, metaMetricsId = TEST_META_METRICS_ID, preferencesStore = getMockPreferencesStore(), - networkController = getMockNetworkController(), + getCurrentChainId = () => FAKE_CHAIN_ID, + onNetworkDidChange = () => { + // do nothing + }, segmentInstance, } = {}) { return new MetaMetricsController({ segment: segmentInstance || segment, - getCurrentChainId: () => - networkController.store.getState().providerConfig.chainId, - onNetworkDidChange: - networkController.onNetworkDidChange.bind(networkController), + getCurrentChainId, + onNetworkDidChange, preferencesStore, version: '0.0.1', environment: 'test', @@ -166,6 +141,7 @@ function getMetaMetricsController({ extension: MOCK_EXTENSION, }); } + describe('MetaMetricsController', function () { const now = new Date(); let clock; @@ -213,17 +189,20 @@ describe('MetaMetricsController', function () { }); it('should update when network changes', function () { - const networkController = getMockNetworkController(); + let chainId = '0x111'; + let networkDidChangeListener; + const onNetworkDidChange = (listener) => { + networkDidChangeListener = listener; + }; const metaMetricsController = getMetaMetricsController({ - networkController, + getCurrentChainId: () => chainId, + onNetworkDidChange, }); - networkController.store.updateState({ - providerConfig: { - type: 'NEW_NETWORK', - chainId: '0xaab', - }, - }); - assert.strictEqual(metaMetricsController.chainId, '0xaab'); + + chainId = '0x222'; + networkDidChangeListener(); + + assert.strictEqual(metaMetricsController.chainId, '0x222'); }); it('should update when preferences changes', function () { diff --git a/app/scripts/controllers/mmi-controller.js b/app/scripts/controllers/mmi-controller.js index ca9933cfb..689472829 100644 --- a/app/scripts/controllers/mmi-controller.js +++ b/app/scripts/controllers/mmi-controller.js @@ -549,22 +549,25 @@ export default class MMIController extends EventEmitter { this.preferencesController.setSelectedAddress(address); } const selectedChainId = parseInt( - this.networkController.getCurrentChainId(), + this.networkController.state.providerConfig.chainId, 16, ); if (selectedChainId !== chainId && chainId === 1) { - this.networkController.setProviderType('mainnet'); + await this.networkController.setProviderType('mainnet'); } else if (selectedChainId !== chainId) { - const network = this.preferencesController - .getFrequentRpcListDetail() - .find((item) => parseInt(item.chainId, 16) === chainId); - this.networkController.setRpcTarget( - network.rpcUrl, - network.chainId, - network.ticker, - network.nickname, - ); + const foundNetworkConfiguration = Object.values( + this.networkController.state.networkConfigurations, + ).find((networkConfiguration) => { + return parseInt(networkConfiguration.chainId, 16) === chainId; + }); + + if (foundNetworkConfiguration !== undefined) { + await this.networkConfiguration.setActiveNetwork( + foundNetworkConfiguration.id, + ); + } } + getPermissionBackgroundApiMethods( this.permissionController, ).addPermittedAccount(origin, address); diff --git a/app/scripts/controllers/network/create-network-client.test.ts b/app/scripts/controllers/network/create-network-client.test.ts deleted file mode 100644 index 7e674392b..000000000 --- a/app/scripts/controllers/network/create-network-client.test.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { NetworkClientType } from './create-network-client'; -import { testsForProviderType } from './provider-api-tests/shared-tests'; - -describe('createNetworkClient', () => { - testsForProviderType(NetworkClientType.Infura); - testsForProviderType(NetworkClientType.Custom); -}); diff --git a/app/scripts/controllers/network/create-network-client.ts b/app/scripts/controllers/network/create-network-client.ts deleted file mode 100644 index 6e96b71f5..000000000 --- a/app/scripts/controllers/network/create-network-client.ts +++ /dev/null @@ -1,191 +0,0 @@ -import { - createAsyncMiddleware, - createScaffoldMiddleware, - JsonRpcEngine, - mergeMiddleware, - JsonRpcMiddleware, -} from 'json-rpc-engine'; -import { - createBlockCacheMiddleware, - createBlockRefMiddleware, - createBlockRefRewriteMiddleware, - createBlockTrackerInspectorMiddleware, - createInflightCacheMiddleware, - createFetchMiddleware, - createRetryOnEmptyMiddleware, -} from '@metamask/eth-json-rpc-middleware'; -import { - providerFromEngine, - providerFromMiddleware, - SafeEventEmitterProvider, -} from '@metamask/eth-json-rpc-provider'; -import { createInfuraMiddleware } from '@metamask/eth-json-rpc-infura'; -import type { Hex } from '@metamask/utils/dist'; -import { PollingBlockTracker } from 'eth-block-tracker/dist'; -import { SECOND } from '../../../../shared/constants/time'; -import { - BUILT_IN_INFURA_NETWORKS, - BuiltInInfuraNetwork, -} from '../../../../shared/constants/network'; - -export enum NetworkClientType { - Custom = 'custom', - Infura = 'infura', -} - -type CustomNetworkConfiguration = { - chainId: Hex; - rpcUrl: string; - type: NetworkClientType.Custom; -}; - -type InfuraNetworkConfiguration = { - network: BuiltInInfuraNetwork; - infuraProjectId: string; - type: NetworkClientType.Infura; -}; - -/** - * Create a JSON RPC network client for a specific network. - * - * @param networkConfig - The network configuration. - * @returns - */ -export function createNetworkClient( - networkConfig: CustomNetworkConfiguration | InfuraNetworkConfiguration, -): { provider: SafeEventEmitterProvider; blockTracker: PollingBlockTracker } { - const rpcApiMiddleware = - networkConfig.type === NetworkClientType.Infura - ? createInfuraMiddleware({ - network: networkConfig.network, - projectId: networkConfig.infuraProjectId, - maxAttempts: 5, - source: 'metamask', - }) - : createFetchMiddleware({ - btoa: global.btoa, - fetch: global.fetch, - rpcUrl: networkConfig.rpcUrl, - }); - - const rpcProvider = providerFromMiddleware(rpcApiMiddleware); - - const blockTrackerOpts = - process.env.IN_TEST && networkConfig.type === 'custom' - ? { pollingInterval: SECOND } - : {}; - const blockTracker = new PollingBlockTracker({ - ...blockTrackerOpts, - provider: rpcProvider, - }); - - const networkMiddleware = - networkConfig.type === NetworkClientType.Infura - ? createInfuraNetworkMiddleware({ - blockTracker, - network: networkConfig.network, - rpcProvider, - rpcApiMiddleware, - }) - : createCustomNetworkMiddleware({ - blockTracker, - chainId: networkConfig.chainId, - rpcApiMiddleware, - }); - - const engine = new JsonRpcEngine(); - - engine.push(networkMiddleware); - - const provider = providerFromEngine(engine); - - return { provider, blockTracker }; -} - -function createInfuraNetworkMiddleware({ - blockTracker, - network, - rpcProvider, - rpcApiMiddleware, -}: { - blockTracker: PollingBlockTracker; - network: BuiltInInfuraNetwork; - rpcProvider: SafeEventEmitterProvider; - rpcApiMiddleware: JsonRpcMiddleware; -}) { - return mergeMiddleware([ - createNetworkAndChainIdMiddleware({ network }), - createBlockCacheMiddleware({ blockTracker }), - createInflightCacheMiddleware(), - createBlockRefMiddleware({ blockTracker, provider: rpcProvider }), - createRetryOnEmptyMiddleware({ blockTracker, provider: rpcProvider }), - createBlockTrackerInspectorMiddleware({ blockTracker }), - rpcApiMiddleware, - ]); -} - -function createNetworkAndChainIdMiddleware({ - network, -}: { - network: BuiltInInfuraNetwork; -}) { - if (!BUILT_IN_INFURA_NETWORKS[network]) { - throw new Error(`createInfuraClient - unknown network "${network}"`); - } - - const { chainId, networkId } = BUILT_IN_INFURA_NETWORKS[network]; - - return createScaffoldMiddleware({ - eth_chainId: chainId, - net_version: networkId, - }); -} - -const createChainIdMiddleware = ( - chainId: string, -): JsonRpcMiddleware => { - return (req, res, next, end) => { - if (req.method === 'eth_chainId') { - res.result = chainId; - return end(); - } - return next(); - }; -}; - -function createCustomNetworkMiddleware({ - blockTracker, - chainId, - rpcApiMiddleware, -}: { - blockTracker: PollingBlockTracker; - chainId: string; - rpcApiMiddleware: any; -}) { - const testMiddlewares = process.env.IN_TEST - ? [createEstimateGasDelayTestMiddleware()] - : []; - - return mergeMiddleware([ - ...testMiddlewares, - createChainIdMiddleware(chainId), - createBlockRefRewriteMiddleware({ blockTracker }), - createBlockCacheMiddleware({ blockTracker }), - createInflightCacheMiddleware(), - createBlockTrackerInspectorMiddleware({ blockTracker }), - rpcApiMiddleware, - ]); -} - -/** - * For use in tests only. - * Adds a delay to `eth_estimateGas` calls. - */ -function createEstimateGasDelayTestMiddleware() { - return createAsyncMiddleware(async (req, _, next) => { - if (req.method === 'eth_estimateGas') { - await new Promise((resolve) => setTimeout(resolve, SECOND * 2)); - } - return next(); - }); -} diff --git a/app/scripts/controllers/network/index.ts b/app/scripts/controllers/network/index.ts deleted file mode 100644 index de3e59ea1..000000000 --- a/app/scripts/controllers/network/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './network-controller'; diff --git a/app/scripts/controllers/network/network-controller.test.ts b/app/scripts/controllers/network/network-controller.test.ts deleted file mode 100644 index cd693f4e8..000000000 --- a/app/scripts/controllers/network/network-controller.test.ts +++ /dev/null @@ -1,7676 +0,0 @@ -// The `refreshNetworkTests` helper defines tests outside of a `describe` block. -/* eslint-disable jest/require-top-level-describe */ - -import { inspect, isDeepStrictEqual, promisify } from 'util'; -import assert from 'assert'; -import { get } from 'lodash'; -import { v4 } from 'uuid'; -import nock from 'nock'; -import { ControllerMessenger } from '@metamask/base-controller'; -import { SafeEventEmitterProvider } from '@metamask/eth-json-rpc-provider'; -import { toHex } from '@metamask/controller-utils'; -import { when, resetAllWhenMocks } from 'jest-when'; -import { ethErrors } from 'eth-rpc-errors'; -import { - NetworkStatus, - NETWORK_TYPES, -} from '../../../../shared/constants/network'; -import { - NetworkController, - NetworkControllerAction, - NetworkControllerEvent, - NetworkControllerOptions, - NetworkControllerState, - ProviderConfiguration, -} from './network-controller'; -import { - createNetworkClient, - NetworkClientType, -} from './create-network-client'; -import { FakeBlockTracker } from './test/fake-block-tracker'; -import { FakeProvider, FakeProviderStub } from './test/fake-provider'; - -jest.mock('./create-network-client'); - -jest.mock('uuid', () => { - const actual = jest.requireActual('uuid'); - - return { - ...actual, - v4: jest.fn(), - }; -}); - -/** - * A block header object that `eth_getBlockByNumber` can be mocked to return. - * Note that this type does not specify all of the properties present within the - * block header; within these tests, we are only interested in `number` and - * `baseFeePerGas`. - */ -type Block = { - number: string; - baseFeePerGas?: string; -}; - -const createNetworkClientMock = jest.mocked(createNetworkClient); -const uuidV4Mock = jest.mocked(v4); - -const NetworkType = { - mainnet: NETWORK_TYPES.MAINNET, - goerli: NETWORK_TYPES.GOERLI, - sepolia: NETWORK_TYPES.SEPOLIA, - rpc: NETWORK_TYPES.RPC, -} as const; - -/** - * A dummy block that matches the pre-EIP-1559 format (i.e. it doesn't have the - * `baseFeePerGas` property). - */ -const PRE_1559_BLOCK: Block = { - number: '0x42', -}; - -/** - * A dummy block that matches the pre-EIP-1559 format (i.e. it has the - * `baseFeePerGas` property). - */ -const POST_1559_BLOCK: Block = { - ...PRE_1559_BLOCK, - baseFeePerGas: '0x63c498a46', -}; - -/** - * An alias for `POST_1559_BLOCK`, for tests that don't care about which kind of - * block they're looking for. - */ -const BLOCK: Block = POST_1559_BLOCK; - -/** - * The networks that NetworkController recognizes as built-in Infura networks, - * along with information we expect to be true for those networks. - */ -const INFURA_NETWORKS = [ - { - networkType: NetworkType.mainnet, - chainId: toHex(1), - ticker: 'ETH', - blockExplorerUrl: 'https://etherscan.io', - }, - { - networkType: NetworkType.goerli, - chainId: toHex(5), - ticker: 'GoerliETH', - blockExplorerUrl: 'https://goerli.etherscan.io', - }, - { - networkType: NetworkType.sepolia, - chainId: toHex(11155111), - ticker: 'SepoliaETH', - blockExplorerUrl: 'https://sepolia.etherscan.io', - }, -]; - -/** - * A response object for a successful request to `eth_getBlockByNumber`. It is - * assumed that the block number here is insignificant to the test. - */ -const SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE = { - result: BLOCK, -}; - -/** - * A response object for a successful request to `net_version`. It is assumed - * that the network ID here is insignificant to the test. - */ -const SUCCESSFUL_NET_VERSION_RESPONSE = { - result: '42', -}; - -/** - * A response object for a request that has been geoblocked by Infura. - */ -const BLOCKED_INFURA_JSON_RPC_ERROR = ethErrors.rpc.internal( - JSON.stringify({ error: 'countryBlocked' }), -); - -/** - * A response object for a unsuccessful request to any RPC method. It is assumed - * that the error here is insignificant to the test. - */ -const GENERIC_JSON_RPC_ERROR = ethErrors.rpc.internal( - JSON.stringify({ error: 'oops' }), -); - -describe('NetworkController', () => { - beforeEach(() => { - // Disable all requests, even those to localhost - nock.disableNetConnect(); - }); - - afterEach(() => { - nock.enableNetConnect('localhost'); - nock.cleanAll(); - resetAllWhenMocks(); - jest.resetAllMocks(); - }); - - describe('constructor', () => { - const invalidInfuraProjectIds = [undefined, null, {}, 1]; - invalidInfuraProjectIds.forEach((invalidProjectId) => { - it(`throws given an invalid Infura ID of "${inspect( - invalidProjectId, - )}"`, () => { - const messenger = buildMessenger(); - const restrictedMessenger = buildNetworkControllerMessenger(messenger); - expect( - () => - new NetworkController({ - messenger: restrictedMessenger, - // @ts-expect-error We are intentionally passing bad input. - infuraProjectId: invalidProjectId, - }), - ).toThrow('Invalid Infura project ID'); - }); - }); - - it('initializes the state with some defaults', async () => { - await withController(({ controller }) => { - expect(controller.store.getState()).toMatchInlineSnapshot(` - { - "networkConfigurations": {}, - "networkDetails": { - "EIPS": {}, - }, - "networkId": null, - "networkStatus": "unknown", - "providerConfig": { - "chainId": "0x539", - "nickname": "Localhost 8545", - "rpcUrl": "http://localhost:8545", - "ticker": "ETH", - "type": "rpc", - }, - } - `); - }); - }); - - it('merges the given state into the default state', async () => { - await withController( - { - state: { - providerConfig: { - type: 'rpc', - rpcUrl: 'http://example-custom-rpc.metamask.io', - chainId: '0x9999' as const, - nickname: 'Test initial state', - }, - networkDetails: { - EIPS: { - 1559: true, - }, - }, - }, - }, - ({ controller }) => { - expect(controller.store.getState()).toMatchInlineSnapshot(` - { - "networkConfigurations": {}, - "networkDetails": { - "EIPS": { - "1559": true, - }, - }, - "networkId": null, - "networkStatus": "unknown", - "providerConfig": { - "chainId": "0x9999", - "nickname": "Test initial state", - "rpcUrl": "http://example-custom-rpc.metamask.io", - "type": "rpc", - }, - } - `); - }, - ); - }); - }); - - describe('destroy', () => { - it('does not throw if called before the provider is initialized', async () => { - await withController(async ({ controller }) => { - expect(await controller.destroy()).toBeUndefined(); - }); - }); - - it('stops the block tracker for the currently selected network as long as the provider has been initialized', async () => { - await withController(async ({ controller }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await controller.initializeProvider(); - const { blockTracker } = controller.getProviderAndBlockTracker(); - assert(blockTracker, 'Block tracker is somehow unset'); - // The block tracker starts running after a listener is attached - blockTracker.addListener('latest', () => { - // do nothing - }); - expect(blockTracker.isRunning()).toBe(true); - - await controller.destroy(); - - expect(blockTracker.isRunning()).toBe(false); - }); - }); - }); - - describe('initializeProvider', () => { - describe('when the type in the provider config is invalid', () => { - it('throws', async () => { - const invalidProviderConfig = {}; - await withController( - /* @ts-expect-error We're intentionally passing bad input. */ - { - state: { - providerConfig: invalidProviderConfig, - }, - }, - async ({ controller }) => { - await expect(async () => { - await controller.initializeProvider(); - }).rejects.toThrow("Unrecognized network type: 'undefined'"); - }, - ); - }); - }); - - for (const { networkType } of INFURA_NETWORKS) { - describe(`when the type in the provider config is "${networkType}"`, () => { - it(`creates a network client for the ${networkType} Infura network, capturing the resulting provider`, async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: networkType, - }), - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'test_method', - params: [], - }, - response: { - result: 'test response', - }, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - createNetworkClientMock.mockReturnValue(fakeNetworkClient); - - await controller.initializeProvider(); - - expect(createNetworkClientMock).toHaveBeenCalledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }); - const { provider } = controller.getProviderAndBlockTracker(); - assert(provider, 'Provider is not set'); - const promisifiedSendAsync = promisify(provider.sendAsync).bind( - provider, - ); - const { result } = await promisifiedSendAsync({ - id: 1, - jsonrpc: '2.0', - method: 'test_method', - params: [], - }); - expect(result).toBe('test response'); - }, - ); - }); - - lookupNetworkTests({ - expectedProviderConfig: buildProviderConfig({ type: networkType }), - initialState: { - providerConfig: buildProviderConfig({ type: networkType }), - }, - operation: async (controller: NetworkController) => { - await controller.initializeProvider(); - }, - }); - }); - } - - describe(`when the type in the provider configuration is "rpc"`, () => { - describe('if chainId and rpcUrl are present in the provider config', () => { - it('creates a network client for a custom RPC endpoint using the provider config, capturing the resulting provider', async () => { - await withController( - { - state: { - providerConfig: { - type: NetworkType.rpc, - chainId: toHex(1337), - rpcUrl: 'http://example.com', - }, - }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'test_method', - params: [], - }, - response: { - result: 'test response', - }, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - createNetworkClientMock.mockReturnValue(fakeNetworkClient); - - await controller.initializeProvider(); - - expect(createNetworkClientMock).toHaveBeenCalledWith({ - chainId: toHex(1337), - rpcUrl: 'http://example.com', - type: NetworkClientType.Custom, - }); - const { provider } = controller.getProviderAndBlockTracker(); - assert(provider, 'Provider is not set'); - const promisifiedSendAsync = promisify(provider.sendAsync).bind( - provider, - ); - const { result } = await promisifiedSendAsync({ - id: 1, - jsonrpc: '2.0', - method: 'test_method', - params: [], - }); - expect(result).toBe('test response'); - }, - ); - }); - - lookupNetworkTests({ - expectedProviderConfig: buildProviderConfig({ - type: NetworkType.rpc, - }), - initialState: { - providerConfig: buildProviderConfig({ - type: NetworkType.rpc, - }), - }, - operation: async (controller: NetworkController) => { - await controller.initializeProvider(); - }, - }); - }); - - describe('if chainId is missing from the provider config', () => { - it('throws', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: NetworkType.rpc, - chainId: undefined, - }), - }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - createNetworkClientMock.mockReturnValue(fakeNetworkClient); - - await expect(() => - controller.initializeProvider(), - ).rejects.toThrow( - 'chainId must be provided for custom RPC endpoints', - ); - }, - ); - }); - - it('does not create a network client or capture a provider', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: NetworkType.rpc, - chainId: undefined, - }), - }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - createNetworkClientMock.mockReturnValue(fakeNetworkClient); - - try { - await controller.initializeProvider(); - } catch { - // ignore the error - } - - expect(createNetworkClientMock).not.toHaveBeenCalled(); - const { provider, blockTracker } = - controller.getProviderAndBlockTracker(); - expect(provider).toBeNull(); - expect(blockTracker).toBeNull(); - }, - ); - }); - }); - - describe('if rpcUrl is missing from the provider config', () => { - it('throws', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: NetworkType.rpc, - rpcUrl: undefined, - }), - }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - createNetworkClientMock.mockReturnValue(fakeNetworkClient); - - await expect(() => - controller.initializeProvider(), - ).rejects.toThrow( - 'rpcUrl must be provided for custom RPC endpoints', - ); - }, - ); - }); - - it('does not create a network client or capture a provider', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: NetworkType.rpc, - rpcUrl: undefined, - }), - }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - createNetworkClientMock.mockReturnValue(fakeNetworkClient); - - try { - await controller.initializeProvider(); - } catch { - // ignore the error - } - - expect(createNetworkClientMock).not.toHaveBeenCalled(); - const { provider, blockTracker } = - controller.getProviderAndBlockTracker(); - expect(provider).toBeNull(); - expect(blockTracker).toBeNull(); - }, - ); - }); - }); - }); - }); - - describe('getProviderAndBlockTracker', () => { - it('returns objects that proxy to the provider and block tracker as long as the provider has been initialized', async () => { - await withController(async ({ controller }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await controller.initializeProvider(); - - const { provider, blockTracker } = - controller.getProviderAndBlockTracker(); - - expect(provider).toHaveProperty('sendAsync'); - expect(blockTracker).toHaveProperty('checkForLatestBlock'); - }); - }); - - it("returns null for both the provider and block tracker if the provider hasn't been initialized yet", async () => { - await withController(async ({ controller }) => { - const { provider, blockTracker } = - controller.getProviderAndBlockTracker(); - - expect(provider).toBeNull(); - expect(blockTracker).toBeNull(); - }); - }); - - for (const { networkType } of INFURA_NETWORKS) { - describe(`when the type in the provider configuration is changed to "${networkType}"`, () => { - it(`returns a provider object that was pointed to another network before the switch and is pointed to "${networkType}" afterward`, async () => { - await withController( - { - state: { - providerConfig: { - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - }, - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ - { - request: { - method: 'test', - }, - response: { - result: 'test response 1', - }, - }, - ]), - buildFakeProvider([ - { - request: { - method: 'test', - }, - response: { - result: 'test response 2', - }, - }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - chainId: '0x1337', - rpcUrl: 'https://mock-rpc-url', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.initializeProvider(); - const { provider } = controller.getProviderAndBlockTracker(); - assert(provider, 'Provider is somehow unset'); - - const promisifiedSendAsync1 = promisify(provider.sendAsync).bind( - provider, - ); - const response1 = await promisifiedSendAsync1({ - id: '1', - jsonrpc: '2.0', - method: 'test', - }); - expect(response1.result).toBe('test response 1'); - - await controller.setProviderType(networkType); - const promisifiedSendAsync2 = promisify(provider.sendAsync).bind( - provider, - ); - const response2 = await promisifiedSendAsync2({ - id: '2', - jsonrpc: '2.0', - method: 'test', - }); - expect(response2.result).toBe('test response 2'); - }, - ); - }); - }); - } - - describe('when the type in the provider configuration is changed to "rpc"', () => { - it('returns a provider object that was pointed to another network before the switch and is pointed to the new network', async () => { - await withController( - { - state: { - providerConfig: { - type: 'goerli', - // NOTE: This doesn't need to match the logical chain ID of - // the network selected, it just needs to exist - chainId: '0x9999999', - }, - networkConfigurations: { - testNetworkConfigurationId: { - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - ticker: 'ABC', - id: 'testNetworkConfigurationId', - }, - }, - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ - { - request: { - method: 'test', - }, - response: { - result: 'test response 1', - }, - }, - ]), - buildFakeProvider([ - { - request: { - method: 'test', - }, - response: { - result: 'test response 2', - }, - }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - network: NetworkType.goerli, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - chainId: '0x1337', - rpcUrl: 'https://mock-rpc-url', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.initializeProvider(); - const { provider } = controller.getProviderAndBlockTracker(); - assert(provider, 'Provider is somehow unset'); - - const promisifiedSendAsync1 = promisify(provider.sendAsync).bind( - provider, - ); - const response1 = await promisifiedSendAsync1({ - id: '1', - jsonrpc: '2.0', - method: 'test', - }); - expect(response1.result).toBe('test response 1'); - - await controller.setActiveNetwork('testNetworkConfigurationId'); - const promisifiedSendAsync2 = promisify(provider.sendAsync).bind( - provider, - ); - const response2 = await promisifiedSendAsync2({ - id: '2', - jsonrpc: '2.0', - method: 'test', - }); - expect(response2.result).toBe('test response 2'); - }, - ); - }); - }); - }); - - describe('lookupNetwork', () => { - describe('if a provider has not been set', () => { - it('does not change network in state', async () => { - await withController(async ({ controller }) => { - const promiseForNetworkChanges = waitForStateChanges({ - controller, - propertyPath: ['networkId'], - }); - - await controller.lookupNetwork(); - - await expect(promiseForNetworkChanges).toNeverResolve(); - }); - }); - }); - - [NetworkType.mainnet, NetworkType.goerli, NetworkType.sepolia].forEach( - (networkType) => { - describe(`when the provider config in state contains a network type of "${networkType}"`, () => { - describe('if the network was switched after the net_version request started but before it completed', () => { - it('stores the network status of the second network, not the first', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ type: networkType }), - networkConfigurations: { - testNetworkConfigurationId: { - id: 'testNetworkConfigurationId', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'ABC', - }, - }, - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ - // Called during provider initialization - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - // Called via `lookupNetwork` directly - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - beforeCompleting: () => { - // Intentionally not awaited because don't want this to - // block the `net_version` request - controller.setActiveNetwork( - 'testNetworkConfigurationId', - ); - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - ]), - buildFakeProvider([ - // Called when switching networks - { - request: { - method: 'net_version', - }, - error: GENERIC_JSON_RPC_ERROR, - }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - chainId: toHex(1337), - rpcUrl: 'https://mock-rpc-url', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.initializeProvider(); - expect(controller.store.getState().networkStatus).toBe( - 'available', - ); - - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - expect(controller.store.getState().networkStatus).toBe( - 'unknown', - ); - }, - ); - }); - - it('stores the ID of the second network, not the first', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ type: networkType }), - networkConfigurations: { - testNetworkConfigurationId: { - id: 'testNetworkConfigurationId', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'ABC', - }, - }, - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ - // Called during provider initialization - { - request: { - method: 'net_version', - }, - response: { - result: '1', - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - // Called via `lookupNetwork` directly - { - request: { - method: 'net_version', - }, - response: { - result: '1', - }, - beforeCompleting: async () => { - // Intentionally not awaited because don't want this to - // block the `net_version` request - controller.setActiveNetwork( - 'testNetworkConfigurationId', - ); - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - ]), - buildFakeProvider([ - // Called when switching networks - { - request: { - method: 'net_version', - }, - response: { - result: '2', - }, - }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - chainId: toHex(1337), - rpcUrl: 'https://mock-rpc-url', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[1]); - await waitForStateChanges({ - controller, - propertyPath: ['networkId'], - operation: async () => { - await controller.initializeProvider(); - }, - }); - expect(controller.store.getState().networkId).toBe('1'); - - await waitForStateChanges({ - controller, - propertyPath: ['networkId'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - expect(controller.store.getState().networkId).toBe('2'); - }, - ); - }); - - it('stores the EIP-1559 support of the second network, not the first', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ type: networkType }), - networkConfigurations: { - testNetworkConfigurationId: { - id: 'testNetworkConfigurationId', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'ABC', - }, - }, - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ - // Called during provider initialization - { - request: { - method: 'net_version', - }, - response: { - result: '1', - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: POST_1559_BLOCK, - }, - }, - // Called via `lookupNetwork` directly - { - request: { - method: 'net_version', - }, - response: { - result: '1', - }, - beforeCompleting: () => { - // Intentionally not awaited because don't want this to - // block the `net_version` request - controller.setActiveNetwork( - 'testNetworkConfigurationId', - ); - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: POST_1559_BLOCK, - }, - }, - ]), - buildFakeProvider([ - // Called when switching networks - { - request: { - method: 'net_version', - }, - response: { - result: '2', - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: PRE_1559_BLOCK, - }, - }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - chainId: toHex(1337), - rpcUrl: 'https://mock-rpc-url', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.initializeProvider(); - expect( - controller.store.getState().networkDetails, - ).toStrictEqual({ - EIPS: { - 1559: true, - }, - }); - - await waitForStateChanges({ - controller, - propertyPath: ['networkDetails'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - expect( - controller.store.getState().networkDetails, - ).toStrictEqual({ - EIPS: { - 1559: false, - }, - }); - }, - ); - }); - - it('emits infuraIsUnblocked, not infuraIsBlocked, assuming that the first network was blocked', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ type: networkType }), - networkConfigurations: { - testNetworkConfigurationId: { - id: 'testNetworkConfigurationId', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'ABC', - }, - }, - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller, messenger }) => { - const fakeProviders = [ - buildFakeProvider([ - // Called during provider initialization - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - // Called via `lookupNetwork` directly - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - beforeCompleting: () => { - // Intentionally not awaited because don't want this to - // block the `net_version` request - controller.setActiveNetwork( - 'testNetworkConfigurationId', - ); - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - error: BLOCKED_INFURA_JSON_RPC_ERROR, - }, - ]), - buildFakeProvider([ - // Called when switching networks - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - chainId: toHex(1337), - rpcUrl: 'https://mock-rpc-url', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.initializeProvider(); - const promiseForInfuraIsUnblockedEvents = - waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsUnblocked', - }); - const promiseForNoInfuraIsBlockedEvents = - waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsBlocked', - count: 0, - }); - - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - await expect( - promiseForInfuraIsUnblockedEvents, - ).toBeFulfilled(); - await expect( - promiseForNoInfuraIsBlockedEvents, - ).toBeFulfilled(); - }, - ); - }); - }); - - describe('if the network was switched after the eth_getBlockByNumber request started but before it completed', () => { - it('stores the network status of the second network, not the first', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ type: networkType }), - networkConfigurations: { - testNetworkConfigurationId: { - id: 'testNetworkConfigurationId', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'ABC', - }, - }, - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ - // Called during provider initialization - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - // Called via `lookupNetwork` directly - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - beforeCompleting: () => { - // Intentionally not awaited because don't want this to - // block the `net_version` request - controller.setActiveNetwork( - 'testNetworkConfigurationId', - ); - }, - }, - ]), - buildFakeProvider([ - // Called when switching networks - { - request: { - method: 'net_version', - }, - error: GENERIC_JSON_RPC_ERROR, - }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - chainId: toHex(1337), - rpcUrl: 'https://mock-rpc-url', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.initializeProvider(); - expect(controller.store.getState().networkStatus).toBe( - 'available', - ); - - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - expect(controller.store.getState().networkStatus).toBe( - 'unknown', - ); - }, - ); - }); - - it('stores the ID of the second network, not the first', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ type: networkType }), - networkConfigurations: { - testNetworkConfigurationId: { - id: 'testNetworkConfigurationId', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'ABC', - }, - }, - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ - // Called during provider initialization - { - request: { - method: 'net_version', - }, - response: { - result: '1', - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - // Called via `lookupNetwork` directly - { - request: { - method: 'net_version', - }, - response: { - result: '1', - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - beforeCompleting: async () => { - // Intentionally not awaited because don't want this to - // block the `net_version` request - controller.setActiveNetwork( - 'testNetworkConfigurationId', - ); - }, - }, - ]), - buildFakeProvider([ - // Called when switching networks - { - request: { - method: 'net_version', - }, - response: { - result: '2', - }, - }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - chainId: toHex(1337), - rpcUrl: 'https://mock-rpc-url', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[1]); - await waitForStateChanges({ - controller, - propertyPath: ['networkId'], - operation: async () => { - await controller.initializeProvider(); - }, - }); - expect(controller.store.getState().networkId).toBe('1'); - - await waitForStateChanges({ - controller, - propertyPath: ['networkId'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - expect(controller.store.getState().networkId).toBe('2'); - }, - ); - }); - - it('stores the EIP-1559 support of the second network, not the first', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ type: networkType }), - networkConfigurations: { - testNetworkConfigurationId: { - id: 'testNetworkConfigurationId', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'ABC', - }, - }, - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ - // Called during provider initialization - { - request: { - method: 'net_version', - }, - response: { - result: '1', - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: POST_1559_BLOCK, - }, - }, - // Called via `lookupNetwork` directly - { - request: { - method: 'net_version', - }, - response: { - result: '1', - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: POST_1559_BLOCK, - }, - beforeCompleting: () => { - // Intentionally not awaited because don't want this to - // block the `net_version` request - controller.setActiveNetwork( - 'testNetworkConfigurationId', - ); - }, - }, - ]), - buildFakeProvider([ - // Called when switching networks - { - request: { - method: 'net_version', - }, - response: { - result: '2', - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: PRE_1559_BLOCK, - }, - }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - chainId: toHex(1337), - rpcUrl: 'https://mock-rpc-url', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.initializeProvider(); - expect( - controller.store.getState().networkDetails, - ).toStrictEqual({ - EIPS: { - 1559: true, - }, - }); - - await waitForStateChanges({ - controller, - propertyPath: ['networkDetails'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - expect( - controller.store.getState().networkDetails, - ).toStrictEqual({ - EIPS: { - 1559: false, - }, - }); - }, - ); - }); - - it('emits infuraIsUnblocked, not infuraIsBlocked, assuming that the first network was blocked', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ type: networkType }), - networkConfigurations: { - testNetworkConfigurationId: { - id: 'testNetworkConfigurationId', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'ABC', - }, - }, - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller, messenger }) => { - const fakeProviders = [ - buildFakeProvider([ - // Called during provider initialization - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - // Called via `lookupNetwork` directly - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - error: BLOCKED_INFURA_JSON_RPC_ERROR, - beforeCompleting: () => { - // Intentionally not awaited because don't want this to - // block the `net_version` request - controller.setActiveNetwork( - 'testNetworkConfigurationId', - ); - }, - }, - ]), - buildFakeProvider([ - // Called when switching networks - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - chainId: toHex(1337), - rpcUrl: 'https://mock-rpc-url', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.initializeProvider(); - const promiseForInfuraIsUnblockedEvents = - waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsUnblocked', - }); - const promiseForNoInfuraIsBlockedEvents = - waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsBlocked', - count: 0, - }); - - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - await expect( - promiseForInfuraIsUnblockedEvents, - ).toBeFulfilled(); - await expect( - promiseForNoInfuraIsBlockedEvents, - ).toBeFulfilled(); - }, - ); - }); - }); - - lookupNetworkTests({ - expectedProviderConfig: buildProviderConfig({ type: networkType }), - initialState: { - providerConfig: buildProviderConfig({ type: networkType }), - }, - operation: async (controller) => { - await controller.lookupNetwork(); - }, - }); - }); - }, - ); - - describe(`when the provider config in state contains a network type of "rpc"`, () => { - describe('if the network was switched after the net_version request started but before it completed', () => { - it('stores the network status of the second network, not the first', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: NetworkType.rpc, - chainId: toHex(1337), - rpcUrl: 'https://mock-rpc-url', - }), - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ - // Called during provider initialization - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - // Called via `lookupNetwork` directly - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - beforeCompleting: () => { - // Intentionally not awaited because don't want this to - // block the `net_version` request - controller.setProviderType(NetworkType.goerli); - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - ]), - buildFakeProvider([ - // Called when switching networks - { - request: { - method: 'net_version', - }, - error: GENERIC_JSON_RPC_ERROR, - }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - chainId: toHex(1337), - rpcUrl: 'https://mock-rpc-url', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - network: NetworkType.goerli, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.initializeProvider(); - expect(controller.store.getState().networkStatus).toBe( - 'available', - ); - - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - expect(controller.store.getState().networkStatus).toBe('unknown'); - }, - ); - }); - - it('stores the ID of the second network, not the first', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: NetworkType.rpc, - chainId: toHex(1337), - rpcUrl: 'https://mock-rpc-url', - }), - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ - // Called during provider initialization - { - request: { - method: 'net_version', - }, - response: { - result: '1', - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - // Called via `lookupNetwork` directly - { - request: { - method: 'net_version', - }, - response: { - result: '1', - }, - beforeCompleting: async () => { - // Intentionally not awaited because don't want this to - // block the `net_version` request - controller.setProviderType(NetworkType.goerli); - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - ]), - buildFakeProvider([ - // Called when switching networks - { - request: { - method: 'net_version', - }, - response: { - result: '2', - }, - }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - chainId: toHex(1337), - rpcUrl: 'https://mock-rpc-url', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - network: NetworkType.goerli, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[1]); - await waitForStateChanges({ - controller, - propertyPath: ['networkId'], - operation: async () => { - await controller.initializeProvider(); - }, - }); - expect(controller.store.getState().networkId).toBe('1'); - - await waitForStateChanges({ - controller, - propertyPath: ['networkId'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - expect(controller.store.getState().networkId).toBe('2'); - }, - ); - }); - - it('stores the EIP-1559 support of the second network, not the first', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: NetworkType.rpc, - chainId: toHex(1337), - rpcUrl: 'https://mock-rpc-url', - }), - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ - // Called during provider initialization - { - request: { - method: 'net_version', - }, - response: { - result: '1', - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: POST_1559_BLOCK, - }, - }, - // Called via `lookupNetwork` directly - { - request: { - method: 'net_version', - }, - response: { - result: '1', - }, - beforeCompleting: () => { - // Intentionally not awaited because don't want this to - // block the `net_version` request - controller.setProviderType(NetworkType.goerli); - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: POST_1559_BLOCK, - }, - }, - ]), - buildFakeProvider([ - // Called when switching networks - { - request: { - method: 'net_version', - }, - response: { - result: '2', - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: PRE_1559_BLOCK, - }, - }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - chainId: toHex(1337), - rpcUrl: 'https://mock-rpc-url', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - network: NetworkType.goerli, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.initializeProvider(); - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: { - 1559: true, - }, - }); - - await waitForStateChanges({ - controller, - propertyPath: ['networkDetails'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: { - 1559: false, - }, - }); - }, - ); - }); - - it('emits infuraIsBlocked, not infuraIsUnblocked, if the second network was blocked and the first network was not', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: NetworkType.rpc, - chainId: toHex(1337), - rpcUrl: 'https://mock-rpc-url', - }), - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller, messenger }) => { - const fakeProviders = [ - buildFakeProvider([ - // Called during provider initialization - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - // Called via `lookupNetwork` directly - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - beforeCompleting: () => { - // Intentionally not awaited because don't want this to - // block the `net_version` request - controller.setProviderType(NetworkType.goerli); - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - ]), - buildFakeProvider([ - // Called when switching networks - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - error: BLOCKED_INFURA_JSON_RPC_ERROR, - }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - chainId: toHex(1337), - rpcUrl: 'https://mock-rpc-url', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - network: NetworkType.goerli, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.initializeProvider(); - const promiseForNoInfuraIsUnblockedEvents = - waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsUnblocked', - count: 0, - }); - const promiseForInfuraIsBlockedEvents = waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsBlocked', - }); - - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - await expect(promiseForNoInfuraIsUnblockedEvents).toBeFulfilled(); - await expect(promiseForInfuraIsBlockedEvents).toBeFulfilled(); - }, - ); - }); - }); - - describe('if the network was switched after the eth_getBlockByNumber request started but before it completed', () => { - it('stores the network status of the second network, not the first', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: NetworkType.rpc, - chainId: toHex(1337), - rpcUrl: 'https://mock-rpc-url', - }), - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ - // Called during provider initialization - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - // Called via `lookupNetwork` directly - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - beforeCompleting: () => { - // Intentionally not awaited because don't want this to - // block the `net_version` request - controller.setProviderType(NetworkType.goerli); - }, - }, - ]), - buildFakeProvider([ - // Called when switching networks - { - request: { - method: 'net_version', - }, - error: GENERIC_JSON_RPC_ERROR, - }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - chainId: toHex(1337), - rpcUrl: 'https://mock-rpc-url', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - network: NetworkType.goerli, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.initializeProvider(); - expect(controller.store.getState().networkStatus).toBe( - 'available', - ); - - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - expect(controller.store.getState().networkStatus).toBe('unknown'); - }, - ); - }); - - it('stores the ID of the second network, not the first', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: NetworkType.rpc, - chainId: toHex(1337), - rpcUrl: 'https://mock-rpc-url', - }), - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ - // Called during provider initialization - { - request: { - method: 'net_version', - }, - response: { - result: '1', - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - // Called via `lookupNetwork` directly - { - request: { - method: 'net_version', - }, - response: { - result: '1', - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - beforeCompleting: async () => { - // Intentionally not awaited because don't want this to - // block the `net_version` request - controller.setProviderType(NetworkType.goerli); - }, - }, - ]), - buildFakeProvider([ - // Called when switching networks - { - request: { - method: 'net_version', - }, - response: { - result: '2', - }, - }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - chainId: toHex(1337), - rpcUrl: 'https://mock-rpc-url', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - network: NetworkType.goerli, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[1]); - await waitForStateChanges({ - controller, - propertyPath: ['networkId'], - operation: async () => { - await controller.initializeProvider(); - }, - }); - expect(controller.store.getState().networkId).toBe('1'); - - await waitForStateChanges({ - controller, - propertyPath: ['networkId'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - expect(controller.store.getState().networkId).toBe('2'); - }, - ); - }); - - it('stores the EIP-1559 support of the second network, not the first', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: NetworkType.rpc, - chainId: toHex(1337), - rpcUrl: 'https://mock-rpc-url', - }), - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ - // Called during provider initialization - { - request: { - method: 'net_version', - }, - response: { - result: '1', - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: POST_1559_BLOCK, - }, - }, - // Called via `lookupNetwork` directly - { - request: { - method: 'net_version', - }, - response: { - result: '1', - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: POST_1559_BLOCK, - }, - beforeCompleting: () => { - // Intentionally not awaited because don't want this to - // block the `net_version` request - controller.setProviderType(NetworkType.goerli); - }, - }, - ]), - buildFakeProvider([ - // Called when switching networks - { - request: { - method: 'net_version', - }, - response: { - result: '2', - }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: PRE_1559_BLOCK, - }, - }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - chainId: toHex(1337), - rpcUrl: 'https://mock-rpc-url', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - network: NetworkType.goerli, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.initializeProvider(); - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: { - 1559: true, - }, - }); - - await waitForStateChanges({ - controller, - propertyPath: ['networkDetails'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: { - 1559: false, - }, - }); - }, - ); - }); - - it('emits infuraIsBlocked, not infuraIsUnblocked, if the second network was blocked and the first network was not', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: NetworkType.rpc, - chainId: toHex(1337), - rpcUrl: 'https://mock-rpc-url', - }), - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller, messenger }) => { - const fakeProviders = [ - buildFakeProvider([ - // Called during provider initialization - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - // Called via `lookupNetwork` directly - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - beforeCompleting: () => { - // Intentionally not awaited because don't want this to - // block the `net_version` request - controller.setProviderType(NetworkType.goerli); - }, - }, - ]), - buildFakeProvider([ - // Called when switching networks - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - error: BLOCKED_INFURA_JSON_RPC_ERROR, - }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - chainId: toHex(1337), - rpcUrl: 'https://mock-rpc-url', - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - network: NetworkType.goerli, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.initializeProvider(); - const promiseForNoInfuraIsUnblockedEvents = - waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsUnblocked', - count: 0, - }); - const promiseForInfuraIsBlockedEvents = waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsBlocked', - }); - - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - operation: async () => { - await controller.lookupNetwork(); - }, - }); - - await expect(promiseForNoInfuraIsUnblockedEvents).toBeFulfilled(); - await expect(promiseForInfuraIsBlockedEvents).toBeFulfilled(); - }, - ); - }); - }); - - lookupNetworkTests({ - expectedProviderConfig: buildProviderConfig({ - type: NetworkType.rpc, - }), - initialState: { - providerConfig: buildProviderConfig({ type: NetworkType.rpc }), - }, - operation: async (controller) => { - await controller.lookupNetwork(); - }, - }); - }); - }); - - describe('setProviderType', () => { - for (const { - networkType, - chainId, - ticker, - blockExplorerUrl, - } of INFURA_NETWORKS) { - describe(`given a network type of "${networkType}"`, () => { - refreshNetworkTests({ - expectedProviderConfig: buildProviderConfig({ - type: networkType, - }), - operation: async (controller) => { - await controller.setProviderType(networkType); - }, - }); - }); - - it(`overwrites the provider configuration using a predetermined chainId, ticker, and blockExplorerUrl for "${networkType}", clearing id, rpcUrl, and nickname`, async () => { - await withController( - { - state: { - providerConfig: { - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: '0x1337', - nickname: 'test-chain', - ticker: 'TEST', - rpcPrefs: { - blockExplorerUrl: 'https://test-block-explorer.com', - }, - }, - }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - - await controller.setProviderType(networkType); - - expect(controller.store.getState().providerConfig).toStrictEqual({ - type: networkType, - rpcUrl: undefined, - chainId, - ticker, - nickname: undefined, - rpcPrefs: { blockExplorerUrl }, - id: undefined, - }); - }, - ); - }); - } - - describe('given a network type of "rpc"', () => { - it('throws because there is no way to switch to a custom RPC endpoint using this method', async () => { - await withController( - { - state: { - providerConfig: { - type: NetworkType.rpc, - rpcUrl: 'http://somethingexisting.com', - chainId: toHex(99999), - ticker: 'something existing', - nickname: 'something existing', - }, - }, - }, - async ({ controller }) => { - await expect(() => - controller.setProviderType(NetworkType.rpc), - ).rejects.toThrow( - 'NetworkController - cannot call "setProviderType" with type "rpc". Use "setActiveNetwork"', - ); - }, - ); - }); - - it("doesn't set a provider", async () => { - await withController(async ({ controller }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - createNetworkClientMock.mockReturnValue(fakeNetworkClient); - - try { - await controller.setProviderType(NetworkType.rpc); - } catch { - // catch the rejection (it is tested above) - } - - expect(createNetworkClientMock).not.toHaveBeenCalled(); - expect(controller.getProviderAndBlockTracker().provider).toBeNull(); - }); - }); - - it('does not update networkDetails.EIPS in state', async () => { - await withController(async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - response: { - result: { - baseFeePerGas: '0x1', - }, - }, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - createNetworkClientMock.mockReturnValue(fakeNetworkClient); - - try { - await controller.setProviderType(NetworkType.rpc); - } catch { - // catch the rejection (it is tested above) - } - - expect( - controller.store.getState().networkDetails.EIPS[1559], - ).toBeUndefined(); - }); - }); - }); - - describe('given an invalid Infura network name', () => { - it('throws', async () => { - await withController(async ({ controller }) => { - await expect(() => - controller.setProviderType('invalid-infura-network'), - ).rejects.toThrow( - new Error('Unknown Infura provider type "invalid-infura-network".'), - ); - }); - }); - }); - }); - - describe('setActiveNetwork', () => { - refreshNetworkTests({ - expectedProviderConfig: { - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(111), - ticker: 'TEST', - nickname: 'something existing', - id: 'testNetworkConfigurationId', - rpcPrefs: undefined, - type: NetworkType.rpc, - }, - initialState: { - networkConfigurations: { - testNetworkConfigurationId: { - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(111), - ticker: 'TEST', - nickname: 'something existing', - id: 'testNetworkConfigurationId', - rpcPrefs: undefined, - }, - }, - }, - operation: async (controller) => { - await controller.setActiveNetwork('testNetworkConfigurationId'); - }, - }); - - describe('if the given ID does not match a network configuration in networkConfigurations', () => { - it('throws', async () => { - await withController( - { - state: { - networkConfigurations: { - testNetworkConfigurationId: { - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(111), - ticker: 'TEST', - id: 'testNetworkConfigurationId', - }, - }, - }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - - await expect(() => - controller.setActiveNetwork('invalidNetworkConfigurationId'), - ).rejects.toThrow( - new Error( - 'networkConfigurationId invalidNetworkConfigurationId does not match a configured networkConfiguration', - ), - ); - }, - ); - }); - }); - - describe('if the network config does not contain an RPC URL', () => { - it('throws', async () => { - await withController( - // @ts-expect-error RPC URL intentionally omitted - { - state: { - providerConfig: { - type: NetworkType.rpc, - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(111), - ticker: 'TEST', - nickname: 'something existing', - rpcPrefs: undefined, - }, - networkConfigurations: { - testNetworkConfigurationId1: { - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(111), - ticker: 'TEST', - nickname: 'something existing', - id: 'testNetworkConfigurationId1', - rpcPrefs: undefined, - }, - testNetworkConfigurationId2: { - rpcUrl: undefined, - chainId: toHex(222), - ticker: 'something existing', - nickname: 'something existing', - id: 'testNetworkConfigurationId2', - rpcPrefs: undefined, - }, - }, - }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - createNetworkClientMock.mockReturnValue(fakeNetworkClient); - - await expect(() => - controller.setActiveNetwork('testNetworkConfigurationId2'), - ).rejects.toThrow( - 'rpcUrl must be provided for custom RPC endpoints', - ); - - expect(createNetworkClientMock).not.toHaveBeenCalled(); - const { provider, blockTracker } = - controller.getProviderAndBlockTracker(); - expect(provider).toBeNull(); - expect(blockTracker).toBeNull(); - }, - ); - }); - }); - - describe('if the network config does not contain a chain ID', () => { - it('throws', async () => { - await withController( - // @ts-expect-error chain ID intentionally omitted - { - state: { - providerConfig: { - type: NetworkType.rpc, - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(111), - ticker: 'TEST', - nickname: 'something existing', - rpcPrefs: undefined, - }, - networkConfigurations: { - testNetworkConfigurationId1: { - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(111), - ticker: 'TEST', - nickname: 'something existing', - id: 'testNetworkConfigurationId1', - rpcPrefs: undefined, - }, - testNetworkConfigurationId2: { - rpcUrl: 'http://somethingexisting.com', - chainId: undefined, - ticker: 'something existing', - nickname: 'something existing', - id: 'testNetworkConfigurationId2', - rpcPrefs: undefined, - }, - }, - }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - createNetworkClientMock.mockReturnValue(fakeNetworkClient); - - await expect(() => - controller.setActiveNetwork('testNetworkConfigurationId2'), - ).rejects.toThrow( - 'chainId must be provided for custom RPC endpoints', - ); - - expect(createNetworkClientMock).not.toHaveBeenCalled(); - const { provider, blockTracker } = - controller.getProviderAndBlockTracker(); - expect(provider).toBeNull(); - expect(blockTracker).toBeNull(); - }, - ); - }); - }); - - it('overwrites the provider configuration given a networkConfigurationId that matches a configured networkConfiguration', async () => { - await withController( - { - state: { - networkConfigurations: { - testNetworkConfiguration: { - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(111), - ticker: 'TEST', - nickname: 'something existing', - id: 'testNetworkConfigurationId', - rpcPrefs: { - blockExplorerUrl: 'https://test-block-explorer-2.com', - }, - }, - }, - }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient() - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(111), - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClient); - - await controller.setActiveNetwork('testNetworkConfiguration'); - - expect(controller.store.getState().providerConfig).toStrictEqual({ - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(111), - ticker: 'TEST', - nickname: 'something existing', - id: 'testNetworkConfigurationId', - rpcPrefs: { - blockExplorerUrl: 'https://test-block-explorer-2.com', - }, - }); - }, - ); - }); - }); - - describe('getEIP1559Compatibility', () => { - describe('if no provider has been set yet', () => { - it('does not make any state changes', async () => { - await withController(async ({ controller }) => { - const promiseForNoStateChanges = waitForStateChanges({ - controller, - count: 0, - operation: async () => { - await controller.getEIP1559Compatibility(); - }, - }); - - expect(Boolean(promiseForNoStateChanges)).toBe(true); - }); - }); - - it('returns false', async () => { - await withController(async ({ controller }) => { - const isEIP1559Compatible = - await controller.getEIP1559Compatibility(); - - expect(isEIP1559Compatible).toBe(false); - }); - }); - }); - - describe('if a provider has been set but networkDetails.EIPS in state already has a "1559" property', () => { - it('does not make any state changes', async () => { - await withController( - { - state: { - networkDetails: { - EIPS: { - 1559: true, - }, - }, - }, - }, - async ({ controller }) => { - setFakeProvider(controller, { - stubLookupNetworkWhileSetting: true, - }); - const promiseForNoStateChanges = waitForStateChanges({ - controller, - count: 0, - operation: async () => { - await controller.getEIP1559Compatibility(); - }, - }); - - expect(Boolean(promiseForNoStateChanges)).toBe(true); - }, - ); - }); - - it('returns the value of the "1559" property', async () => { - await withController( - { - state: { - networkDetails: { - EIPS: { - 1559: true, - }, - }, - }, - }, - async ({ controller }) => { - setFakeProvider(controller, { - stubLookupNetworkWhileSetting: true, - }); - const isEIP1559Compatible = - await controller.getEIP1559Compatibility(); - - expect(isEIP1559Compatible).toBe(true); - }, - ); - }); - }); - - describe('if a provider has been set and networkDetails.EIPS in state does not already have a "1559" property', () => { - describe('if the request for the latest block is successful', () => { - describe('if the latest block has a "baseFeePerGas" property', () => { - it('sets the "1559" property to true', async () => { - await withController(async ({ controller }) => { - setFakeProvider(controller, { - stubs: [ - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - response: { - result: POST_1559_BLOCK, - }, - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - await controller.getEIP1559Compatibility(); - - expect( - controller.store.getState().networkDetails.EIPS[1559], - ).toBe(true); - }); - }); - - it('returns true', async () => { - await withController(async ({ controller }) => { - setFakeProvider(controller, { - stubs: [ - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - response: { - result: POST_1559_BLOCK, - }, - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - const isEIP1559Compatible = - await controller.getEIP1559Compatibility(); - - expect(isEIP1559Compatible).toBe(true); - }); - }); - }); - - describe('if the latest block does not have a "baseFeePerGas" property', () => { - it('sets the "1559" property to false', async () => { - await withController(async ({ controller }) => { - setFakeProvider(controller, { - stubs: [ - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - response: { - result: PRE_1559_BLOCK, - }, - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - await controller.getEIP1559Compatibility(); - - expect( - controller.store.getState().networkDetails.EIPS[1559], - ).toBe(false); - }); - }); - - it('returns false', async () => { - await withController(async ({ controller }) => { - setFakeProvider(controller, { - stubs: [ - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - response: { - result: PRE_1559_BLOCK, - }, - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - const isEIP1559Compatible = - await controller.getEIP1559Compatibility(); - - expect(isEIP1559Compatible).toBe(false); - }); - }); - }); - - describe('if the request for the latest block responds with null', () => { - it('sets the "1559" property to false', async () => { - await withController(async ({ controller }) => { - setFakeProvider(controller, { - stubs: [ - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - response: { - result: null, - }, - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - await controller.getEIP1559Compatibility(); - - expect( - controller.store.getState().networkDetails.EIPS[1559], - ).toBe(false); - }); - }); - - it('returns false', async () => { - await withController(async ({ controller }) => { - setFakeProvider(controller, { - stubs: [ - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - response: { - result: null, - }, - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - const isEIP1559Compatible = - await controller.getEIP1559Compatibility(); - - expect(isEIP1559Compatible).toBe(false); - }); - }); - }); - }); - - describe('if the request for the latest block is unsuccessful', () => { - it('does not make any state changes', async () => { - await withController(async ({ controller }) => { - setFakeProvider(controller, { - stubs: [ - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - error: GENERIC_JSON_RPC_ERROR, - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - const promiseForNoStateChanges = waitForStateChanges({ - controller, - count: 0, - operation: async () => { - try { - await controller.getEIP1559Compatibility(); - } catch (error) { - // ignore error - } - }, - }); - - expect(Boolean(promiseForNoStateChanges)).toBe(true); - }); - }); - }); - }); - }); - - describe('resetConnection', () => { - [NetworkType.mainnet, NetworkType.goerli, NetworkType.sepolia].forEach( - (networkType) => { - describe(`when the type in the provider configuration is "${networkType}"`, () => { - refreshNetworkTests({ - expectedProviderConfig: buildProviderConfig({ type: networkType }), - initialState: { - providerConfig: buildProviderConfig({ type: networkType }), - }, - operation: async (controller) => { - await controller.resetConnection(); - }, - }); - }); - }, - ); - - describe(`when the type in the provider configuration is "rpc"`, () => { - refreshNetworkTests({ - expectedProviderConfig: buildProviderConfig({ - type: NetworkType.rpc, - }), - initialState: { - providerConfig: buildProviderConfig({ type: NetworkType.rpc }), - }, - operation: async (controller) => { - await controller.resetConnection(); - }, - }); - }); - }); - - describe('NetworkController:getProviderConfig action', () => { - it('returns the provider config in state', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: NetworkType.mainnet, - }), - }, - }, - async ({ messenger }) => { - const providerConfig = await messenger.call( - 'NetworkController:getProviderConfig', - ); - - expect(providerConfig).toStrictEqual( - buildProviderConfig({ - type: NetworkType.mainnet, - }), - ); - }, - ); - }); - }); - - describe('NetworkController:getEthQuery action', () => { - it('returns a EthQuery object that can be used to make requests to the currently selected network', async () => { - await withController(async ({ controller, messenger }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { - method: 'test_method', - params: [], - }, - response: { - result: 'test response', - }, - }, - ], - }); - - const ethQuery = messenger.call('NetworkController:getEthQuery'); - assert(ethQuery, 'ethQuery is not set'); - - const promisifiedSendAsync = promisify(ethQuery.sendAsync).bind( - ethQuery, - ); - const result = await promisifiedSendAsync({ - id: 1, - jsonrpc: '2.0', - method: 'test_method', - params: [], - }); - expect(result).toBe('test response'); - }); - }); - - it('returns undefined if the provider has not been set yet', async () => { - await withController(({ messenger }) => { - const ethQuery = messenger.call('NetworkController:getEthQuery'); - - expect(ethQuery).toBeUndefined(); - }); - }); - }); - - describe('upsertNetworkConfiguration', () => { - it('adds the given network configuration when its rpcURL does not match an existing configuration', async () => { - uuidV4Mock.mockImplementationOnce(() => 'network-configuration-id-1'); - - await withController(async ({ controller }) => { - const rpcUrlNetwork = { - chainId: toHex(9999), - rpcUrl: 'https://test-rpc.com', - ticker: 'RPC', - }; - - expect(controller.store.getState().networkConfigurations).toStrictEqual( - {}, - ); - - await controller.upsertNetworkConfiguration(rpcUrlNetwork, { - referrer: 'https://test-dapp.com', - source: 'dapp', - }); - - expect( - Object.values(controller.store.getState().networkConfigurations), - ).toStrictEqual( - expect.arrayContaining([ - { - ...rpcUrlNetwork, - nickname: undefined, - rpcPrefs: undefined, - id: 'network-configuration-id-1', - }, - ]), - ); - }); - }); - - it('update a network configuration when the configuration being added has an rpcURL that matches an existing configuration', async () => { - await withController( - { - state: { - networkConfigurations: { - testNetworkConfigurationId: { - rpcUrl: 'https://rpc-url.com', - ticker: 'old_rpc_ticker', - nickname: 'old_rpc_nickname', - rpcPrefs: { blockExplorerUrl: 'testchainscan.io' }, - chainId: toHex(1), - id: 'testNetworkConfigurationId', - }, - }, - }, - }, - async ({ controller }) => { - await controller.upsertNetworkConfiguration( - { - rpcUrl: 'https://rpc-url.com', - ticker: 'new_rpc_ticker', - nickname: 'new_rpc_nickname', - rpcPrefs: { blockExplorerUrl: 'alternativetestchainscan.io' }, - chainId: toHex(1), - }, - { referrer: 'https://test-dapp.com', source: 'dapp' }, - ); - expect( - Object.values(controller.store.getState().networkConfigurations), - ).toStrictEqual( - expect.arrayContaining([ - { - rpcUrl: 'https://rpc-url.com', - nickname: 'new_rpc_nickname', - ticker: 'new_rpc_ticker', - rpcPrefs: { blockExplorerUrl: 'alternativetestchainscan.io' }, - chainId: toHex(1), - id: 'testNetworkConfigurationId', - }, - ]), - ); - }, - ); - }); - - it('throws if the given chain ID is not a 0x-prefixed hex number', async () => { - const invalidChainId = '1'; - await withController(async ({ controller }) => { - await expect(async () => - controller.upsertNetworkConfiguration( - { - // @ts-expect-error Intentionally invalid - chainId: invalidChainId, - nickname: 'RPC', - rpcPrefs: { blockExplorerUrl: 'test-block-explorer.com' }, - rpcUrl: 'rpc_url', - ticker: 'RPC', - }, - { - referrer: 'https://test-dapp.com', - source: 'dapp', - }, - ), - ).rejects.toThrow( - new Error( - `Invalid chain ID "${invalidChainId}": invalid hex string.`, - ), - ); - }); - }); - - it('throws if the given chain ID is greater than the maximum allowed ID', async () => { - await withController(async ({ controller }) => { - await expect(async () => - controller.upsertNetworkConfiguration( - { - chainId: '0xFFFFFFFFFFFFFFFF', - nickname: 'RPC', - rpcPrefs: { blockExplorerUrl: 'test-block-explorer.com' }, - rpcUrl: 'rpc_url', - ticker: 'RPC', - }, - { - referrer: 'https://test-dapp.com', - source: 'dapp', - }, - ), - ).rejects.toThrow( - new Error( - 'Invalid chain ID "0xFFFFFFFFFFFFFFFF": numerical value greater than max safe value.', - ), - ); - }); - }); - - it('throws if the no (or a falsy) rpcUrl is passed', async () => { - await withController(async ({ controller }) => { - await expect(() => - controller.upsertNetworkConfiguration( - /* @ts-expect-error We are intentionally passing bad input. */ - { - chainId: toHex(9999), - nickname: 'RPC', - rpcPrefs: { blockExplorerUrl: 'test-block-explorer.com' }, - ticker: 'RPC', - }, - { - referrer: 'https://test-dapp.com', - source: 'dapp', - }, - ), - ).rejects.toThrow( - new Error( - 'An rpcUrl is required to add or update network configuration', - ), - ); - }); - }); - - it('throws if rpcUrl passed is not a valid Url', async () => { - await withController(async ({ controller }) => { - await expect(async () => - controller.upsertNetworkConfiguration( - { - chainId: toHex(9999), - nickname: 'RPC', - rpcPrefs: { blockExplorerUrl: 'test-block-explorer.com' }, - ticker: 'RPC', - rpcUrl: 'test', - }, - { - referrer: 'https://test-dapp.com', - source: 'dapp', - }, - ), - ).rejects.toThrow(new Error('rpcUrl must be a valid URL')); - }); - }); - - it('throws if the no (or a falsy) ticker is passed', async () => { - await withController(async ({ controller }) => { - await expect(async () => - controller.upsertNetworkConfiguration( - // @ts-expect-error - we want to test the case where no ticker is present. - { - chainId: toHex(5), - nickname: 'RPC', - rpcPrefs: { blockExplorerUrl: 'test-block-explorer.com' }, - rpcUrl: 'https://mock-rpc-url', - }, - { - referrer: 'https://test-dapp.com', - source: 'dapp', - }, - ), - ).rejects.toThrow( - new Error( - 'A ticker is required to add or update networkConfiguration', - ), - ); - }); - }); - - it('throws if an options object is not passed as a second argument', async () => { - await withController(async ({ controller }) => { - await expect(async () => - // @ts-expect-error - we want to test the case where no second arg is passed. - controller.upsertNetworkConfiguration({ - chainId: toHex(5), - nickname: 'RPC', - rpcPrefs: { blockExplorerUrl: 'test-block-explorer.com' }, - rpcUrl: 'https://mock-rpc-url', - }), - ).rejects.toThrow('Cannot read properties of undefined'); - }); - }); - - it('throws if referrer and source arguments are not passed', async () => { - uuidV4Mock.mockImplementationOnce(() => 'networkConfigurationId'); - const trackEventSpy = jest.fn(); - await withController( - { - state: { - providerConfig: { - type: NetworkType.rpc, - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(111), - ticker: 'TEST', - id: 'testNetworkConfigurationId', - }, - networkConfigurations: { - testNetworkConfigurationId: { - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(111), - ticker: 'TEST', - id: 'testNetworkConfigurationId', - nickname: undefined, - rpcPrefs: undefined, - }, - }, - }, - trackMetaMetricsEvent: trackEventSpy, - }, - async ({ controller }) => { - const newNetworkConfiguration = { - rpcUrl: 'https://new-chain-rpc-url', - chainId: toHex(222), - ticker: 'NEW', - nickname: 'new-chain', - rpcPrefs: { blockExplorerUrl: 'https://block-explorer' }, - }; - - await expect(async () => - // @ts-expect-error - we want to test the case where the options object is empty. - controller.upsertNetworkConfiguration(newNetworkConfiguration, {}), - ).rejects.toThrow( - 'referrer and source are required arguments for adding or updating a network configuration', - ); - }, - ); - }); - - it('should add the given network if all required properties are present but nither rpcPrefs nor nickname properties are passed', async () => { - uuidV4Mock.mockImplementationOnce(() => 'networkConfigurationId'); - await withController( - { - state: { - networkConfigurations: {}, - }, - }, - async ({ controller }) => { - const rpcUrlNetwork = { - chainId: toHex(1), - rpcUrl: 'https://test-rpc-url', - ticker: 'test_ticker', - }; - - await controller.upsertNetworkConfiguration(rpcUrlNetwork, { - referrer: 'https://test-dapp.com', - source: 'dapp', - }); - - expect( - Object.values(controller.store.getState().networkConfigurations), - ).toStrictEqual( - expect.arrayContaining([ - { - ...rpcUrlNetwork, - nickname: undefined, - rpcPrefs: undefined, - id: 'networkConfigurationId', - }, - ]), - ); - }, - ); - }); - - it('adds new networkConfiguration to networkController store, but only adds valid properties (rpcUrl, chainId, ticker, nickname, rpcPrefs) and fills any missing properties from this list as undefined', async function () { - uuidV4Mock.mockImplementationOnce(() => 'networkConfigurationId'); - await withController( - { - state: { - networkConfigurations: {}, - }, - }, - async ({ controller }) => { - const rpcUrlNetwork = { - chainId: toHex(1), - rpcUrl: 'https://test-rpc-url', - ticker: 'test_ticker', - invalidKey: 'new-chain', - invalidKey2: {}, - }; - - await controller.upsertNetworkConfiguration(rpcUrlNetwork, { - referrer: 'https://test-dapp.com', - source: 'dapp', - }); - - expect( - Object.values(controller.store.getState().networkConfigurations), - ).toStrictEqual( - expect.arrayContaining([ - { - chainId: toHex(1), - rpcUrl: 'https://test-rpc-url', - ticker: 'test_ticker', - nickname: undefined, - rpcPrefs: undefined, - id: 'networkConfigurationId', - }, - ]), - ); - }, - ); - }); - - it('should add the given network configuration if its rpcURL does not match an existing configuration without changing or overwriting other configurations', async () => { - uuidV4Mock.mockImplementationOnce(() => 'networkConfigurationId2'); - await withController( - { - state: { - networkConfigurations: { - networkConfigurationId: { - rpcUrl: 'https://test-rpc-url', - ticker: 'ticker', - nickname: 'nickname', - rpcPrefs: { blockExplorerUrl: 'testchainscan.io' }, - chainId: toHex(1), - id: 'networkConfigurationId', - }, - }, - }, - }, - async ({ controller }) => { - const rpcUrlNetwork = { - chainId: toHex(1), - nickname: 'RPC', - rpcPrefs: undefined, - rpcUrl: 'https://test-rpc-url-2', - ticker: 'RPC', - }; - - await controller.upsertNetworkConfiguration(rpcUrlNetwork, { - referrer: 'https://test-dapp.com', - source: 'dapp', - }); - - expect( - Object.values(controller.store.getState().networkConfigurations), - ).toStrictEqual( - expect.arrayContaining([ - { - rpcUrl: 'https://test-rpc-url', - ticker: 'ticker', - nickname: 'nickname', - rpcPrefs: { blockExplorerUrl: 'testchainscan.io' }, - chainId: toHex(1), - id: 'networkConfigurationId', - }, - { ...rpcUrlNetwork, id: 'networkConfigurationId2' }, - ]), - ); - }, - ); - }); - - it('should use the given configuration to update an existing network configuration that has a matching rpcUrl', async () => { - await withController( - { - state: { - networkConfigurations: { - networkConfigurationId: { - rpcUrl: 'https://test-rpc-url', - ticker: 'old_rpc_ticker', - nickname: 'old_rpc_chainName', - rpcPrefs: { blockExplorerUrl: 'testchainscan.io' }, - chainId: toHex(1), - id: 'networkConfigurationId', - }, - }, - }, - }, - - async ({ controller }) => { - const updatedConfiguration = { - rpcUrl: 'https://test-rpc-url', - ticker: 'new_rpc_ticker', - nickname: 'new_rpc_chainName', - rpcPrefs: { blockExplorerUrl: 'alternativetestchainscan.io' }, - chainId: toHex(1), - }; - await controller.upsertNetworkConfiguration(updatedConfiguration, { - referrer: 'https://test-dapp.com', - source: 'dapp', - }); - expect( - Object.values(controller.store.getState().networkConfigurations), - ).toStrictEqual([ - { - rpcUrl: 'https://test-rpc-url', - nickname: 'new_rpc_chainName', - ticker: 'new_rpc_ticker', - rpcPrefs: { blockExplorerUrl: 'alternativetestchainscan.io' }, - chainId: toHex(1), - id: 'networkConfigurationId', - }, - ]); - }, - ); - }); - - it('should use the given configuration to update an existing network configuration that has a matching rpcUrl without changing or overwriting other networkConfigurations', async () => { - await withController( - { - state: { - networkConfigurations: { - networkConfigurationId: { - rpcUrl: 'https://test-rpc-url', - ticker: 'ticker', - nickname: 'nickname', - rpcPrefs: { blockExplorerUrl: 'testchainscan.io' }, - chainId: toHex(1), - id: 'networkConfigurationId', - }, - networkConfigurationId2: { - rpcUrl: 'https://test-rpc-url-2', - ticker: 'ticker-2', - nickname: 'nickname-2', - rpcPrefs: { blockExplorerUrl: 'testchainscan.io' }, - chainId: toHex(9999), - id: 'networkConfigurationId2', - }, - }, - }, - }, - async ({ controller }) => { - await controller.upsertNetworkConfiguration( - { - rpcUrl: 'https://test-rpc-url', - ticker: 'new-ticker', - nickname: 'new-nickname', - rpcPrefs: { blockExplorerUrl: 'alternativetestchainscan.io' }, - chainId: toHex(1), - }, - { - referrer: 'https://test-dapp.com', - source: 'dapp', - }, - ); - - expect( - Object.values(controller.store.getState().networkConfigurations), - ).toStrictEqual([ - { - rpcUrl: 'https://test-rpc-url', - ticker: 'new-ticker', - nickname: 'new-nickname', - rpcPrefs: { blockExplorerUrl: 'alternativetestchainscan.io' }, - chainId: toHex(1), - id: 'networkConfigurationId', - }, - { - rpcUrl: 'https://test-rpc-url-2', - ticker: 'ticker-2', - nickname: 'nickname-2', - rpcPrefs: { blockExplorerUrl: 'testchainscan.io' }, - chainId: toHex(9999), - id: 'networkConfigurationId2', - }, - ]); - }, - ); - }); - - it('should add the given network and not set it to active if the setActive option is not passed (or a falsy value is passed)', async () => { - uuidV4Mock.mockImplementationOnce(() => 'networkConfigurationId'); - const originalProvider = { - type: NetworkType.rpc, - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(111), - ticker: 'TEST', - id: 'testNetworkConfigurationId', - }; - await withController( - { - state: { - providerConfig: originalProvider, - networkConfigurations: { - testNetworkConfigurationId: { - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(111), - ticker: 'TEST', - id: 'testNetworkConfigurationId', - nickname: undefined, - rpcPrefs: undefined, - }, - }, - }, - }, - async ({ controller }) => { - const rpcUrlNetwork = { - chainId: toHex(222), - rpcUrl: 'https://test-rpc-url', - ticker: 'test_ticker', - }; - - await controller.upsertNetworkConfiguration(rpcUrlNetwork, { - referrer: 'https://test-dapp.com', - source: 'dapp', - }); - - expect(controller.store.getState().providerConfig).toStrictEqual( - originalProvider, - ); - }, - ); - }); - - it('should add the given network and set it to active if the setActive option is passed as true', async () => { - uuidV4Mock.mockImplementationOnce(() => 'networkConfigurationId'); - await withController( - { - state: { - providerConfig: { - type: NetworkType.rpc, - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(111), - ticker: 'TEST', - id: 'testNetworkConfigurationId', - nickname: undefined, - rpcPrefs: undefined, - }, - networkConfigurations: { - testNetworkConfigurationId: { - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(111), - ticker: 'TEST', - id: 'testNetworkConfigurationId', - nickname: undefined, - rpcPrefs: undefined, - }, - }, - }, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - createNetworkClientMock.mockReturnValue(fakeNetworkClient); - const rpcUrlNetwork = { - rpcUrl: 'https://test-rpc-url', - chainId: toHex(222), - ticker: 'test_ticker', - }; - - await controller.upsertNetworkConfiguration(rpcUrlNetwork, { - setActive: true, - referrer: 'https://test-dapp.com', - source: 'dapp', - }); - - expect(controller.store.getState().providerConfig).toStrictEqual({ - type: 'rpc', - rpcUrl: 'https://test-rpc-url', - chainId: toHex(222), - ticker: 'test_ticker', - id: 'networkConfigurationId', - nickname: undefined, - rpcPrefs: undefined, - }); - }, - ); - }); - - it('adds new networkConfiguration to networkController store and calls to the metametrics event tracking with the correct values', async () => { - uuidV4Mock.mockImplementationOnce(() => 'networkConfigurationId'); - const trackEventSpy = jest.fn(); - await withController( - { - state: { - providerConfig: { - type: NetworkType.rpc, - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(111), - ticker: 'TEST', - id: 'testNetworkConfigurationId', - nickname: undefined, - rpcPrefs: undefined, - }, - networkConfigurations: { - testNetworkConfigurationId: { - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(111), - ticker: 'TEST', - id: 'testNetworkConfigurationId', - nickname: undefined, - rpcPrefs: undefined, - }, - }, - }, - trackMetaMetricsEvent: trackEventSpy, - }, - async ({ controller }) => { - const newNetworkConfiguration = { - rpcUrl: 'https://new-chain-rpc-url', - chainId: toHex(222), - ticker: 'NEW', - nickname: 'new-chain', - rpcPrefs: { blockExplorerUrl: 'https://block-explorer' }, - }; - - await controller.upsertNetworkConfiguration(newNetworkConfiguration, { - referrer: 'https://test-dapp.com', - source: 'dapp', - }); - - expect( - Object.values(controller.store.getState().networkConfigurations), - ).toStrictEqual([ - { - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(111), - ticker: 'TEST', - id: 'testNetworkConfigurationId', - nickname: undefined, - rpcPrefs: undefined, - }, - { - ...newNetworkConfiguration, - id: 'networkConfigurationId', - }, - ]); - expect(trackEventSpy).toHaveBeenCalledWith({ - event: 'Custom Network Added', - category: 'Network', - referrer: { - url: 'https://test-dapp.com', - }, - properties: { - chain_id: toHex(222), - symbol: 'NEW', - source: 'dapp', - }, - }); - }, - ); - }); - }); - - describe('removeNetworkConfigurations', () => { - it('remove a network configuration', async () => { - const testNetworkConfigurationId = 'testNetworkConfigurationId'; - await withController( - { - state: { - networkConfigurations: { - [testNetworkConfigurationId]: { - rpcUrl: 'https://rpc-url.com', - ticker: 'old_rpc_ticker', - nickname: 'old_rpc_nickname', - rpcPrefs: { blockExplorerUrl: 'testchainscan.io' }, - chainId: toHex(1337), - id: testNetworkConfigurationId, - }, - }, - }, - }, - async ({ controller }) => { - controller.removeNetworkConfiguration(testNetworkConfigurationId); - expect( - controller.store.getState().networkConfigurations, - ).toStrictEqual({}); - }, - ); - }); - - it('throws if the networkConfigurationId it is passed does not correspond to a network configuration in state', async () => { - const testNetworkConfigurationId = 'testNetworkConfigurationId'; - const invalidNetworkConfigurationId = 'invalidNetworkConfigurationId'; - await withController( - { - state: { - networkConfigurations: { - [testNetworkConfigurationId]: { - rpcUrl: 'https://rpc-url.com', - ticker: 'old_rpc_ticker', - nickname: 'old_rpc_nickname', - rpcPrefs: { blockExplorerUrl: 'testchainscan.io' }, - chainId: toHex(1337), - id: testNetworkConfigurationId, - }, - }, - }, - }, - async ({ controller }) => { - expect(() => - controller.removeNetworkConfiguration( - invalidNetworkConfigurationId, - ), - ).toThrow( - `networkConfigurationId ${invalidNetworkConfigurationId} does not match a configured networkConfiguration`, - ); - }, - ); - }); - }); - - describe('rollbackToPreviousProvider', () => { - for (const { networkType } of INFURA_NETWORKS) { - describe(`if the previous provider configuration had a type of "${networkType}"`, () => { - it('emits networkWillChange', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: networkType, - }), - networkConfigurations: { - testNetworkConfiguration: { - id: 'testNetworkConfiguration', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'TEST', - nickname: 'test network', - rpcPrefs: { - blockExplorerUrl: 'https://test-block-explorer.com', - }, - }, - }, - }, - }, - async ({ controller, messenger }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await controller.setActiveNetwork('testNetworkConfiguration'); - - const networkWillChange = waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:networkWillChange', - operation: () => { - // Intentionally not awaited because we're capturing an event - // emitted partway through the operation - controller.rollbackToPreviousProvider(); - }, - }); - - await expect(networkWillChange).toBeFulfilled(); - }, - ); - }); - - it('emits networkDidChange', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: networkType, - }), - networkConfigurations: { - testNetworkConfiguration: { - id: 'testNetworkConfiguration', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'TEST', - nickname: 'test network', - rpcPrefs: { - blockExplorerUrl: 'https://test-block-explorer.com', - }, - }, - }, - }, - }, - async ({ controller, messenger }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await controller.setActiveNetwork('testNetworkConfiguration'); - - const networkDidChange = waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:networkDidChange', - operation: () => { - // Intentionally not awaited because we're capturing an event - // emitted partway through the operation - controller.rollbackToPreviousProvider(); - }, - }); - - await expect(networkDidChange).toBeFulfilled(); - }, - ); - }); - - it('overwrites the the current provider configuration with the previous provider configuration', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: networkType, - }), - networkConfigurations: { - testNetworkConfiguration: { - id: 'testNetworkConfiguration', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'TEST', - nickname: 'test network', - rpcPrefs: { - blockExplorerUrl: 'https://test-block-explorer.com', - }, - }, - }, - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [buildFakeProvider(), buildFakeProvider()]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.setActiveNetwork('testNetworkConfiguration'); - expect(controller.store.getState().providerConfig).toStrictEqual({ - type: 'rpc', - id: 'testNetworkConfiguration', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'TEST', - nickname: 'test network', - rpcPrefs: { - blockExplorerUrl: 'https://test-block-explorer.com', - }, - }); - - await controller.rollbackToPreviousProvider(); - - expect(controller.store.getState().providerConfig).toStrictEqual( - buildProviderConfig({ - type: networkType, - }), - ); - }, - ); - }); - - it('resets the network status to "unknown" before updating the provider', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: networkType, - }), - networkConfigurations: { - testNetworkConfiguration: { - id: 'testNetworkConfiguration', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'TEST', - }, - }, - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - ]), - buildFakeProvider(), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.setActiveNetwork('testNetworkConfiguration'); - expect(controller.store.getState().networkStatus).toBe( - 'available', - ); - - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - // We only care about the first state change, because it - // happens before networkDidChange - count: 1, - operation: () => { - // Intentionally not awaited because we want to check state - // while this operation is in-progress - controller.rollbackToPreviousProvider(); - }, - beforeResolving: () => { - expect(controller.store.getState().networkStatus).toBe( - 'unknown', - ); - }, - }); - }, - ); - }); - - it('clears EIP-1559 support for the network from state before updating the provider', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: networkType, - }), - networkConfigurations: { - testNetworkConfiguration: { - id: 'testNetworkConfiguration', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'TEST', - }, - }, - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: POST_1559_BLOCK, - }, - }, - ]), - buildFakeProvider(), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.setActiveNetwork('testNetworkConfiguration'); - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: { - 1559: true, - }, - }); - - await waitForStateChanges({ - controller, - propertyPath: ['networkDetails'], - // We only care about the first state change, because it - // happens before networkDidChange - count: 1, - operation: () => { - // Intentionally not awaited because we want to check state - // while this operation is in-progress - controller.rollbackToPreviousProvider(); - }, - beforeResolving: () => { - expect( - controller.store.getState().networkDetails, - ).toStrictEqual({ - EIPS: {}, - }); - }, - }); - }, - ); - }); - - it(`initializes a provider pointed to the "${networkType}" Infura network`, async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: networkType, - }), - networkConfigurations: { - testNetworkConfiguration: { - id: 'testNetworkConfiguration', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'TEST', - }, - }, - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider(), - buildFakeProvider([ - { - request: { - method: 'test', - }, - response: { - result: 'test response', - }, - }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.setActiveNetwork('testNetworkConfiguration'); - - await controller.rollbackToPreviousProvider(); - - const { provider } = controller.getProviderAndBlockTracker(); - assert(provider, 'Provider is somehow unset'); - const promisifiedSendAsync = promisify(provider.sendAsync).bind( - provider, - ); - const response = await promisifiedSendAsync({ - id: '1', - jsonrpc: '2.0', - method: 'test', - }); - expect(response.result).toBe('test response'); - }, - ); - }); - - it('replaces the provider object underlying the provider proxy without creating a new instance of the proxy itself', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: networkType, - }), - networkConfigurations: { - testNetworkConfiguration: { - id: 'testNetworkConfiguration', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'TEST', - }, - }, - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [buildFakeProvider(), buildFakeProvider()]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.setActiveNetwork('testNetworkConfiguration'); - const { provider: providerBefore } = - controller.getProviderAndBlockTracker(); - - await controller.rollbackToPreviousProvider(); - - const { provider: providerAfter } = - controller.getProviderAndBlockTracker(); - expect(providerBefore).toBe(providerAfter); - }, - ); - }); - - it('emits infuraIsBlocked or infuraIsUnblocked, depending on whether Infura is blocking requests for the previous network', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: networkType, - }), - networkConfigurations: { - testNetworkConfiguration: { - id: 'testNetworkConfiguration', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'TEST', - }, - }, - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller, messenger }) => { - const fakeProviders = [ - buildFakeProvider(), - buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - }, - error: BLOCKED_INFURA_JSON_RPC_ERROR, - }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.setActiveNetwork('testNetworkConfiguration'); - const promiseForNoInfuraIsUnblockedEvents = - waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsUnblocked', - count: 0, - }); - const promiseForInfuraIsBlocked = waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsBlocked', - }); - - await controller.rollbackToPreviousProvider(); - - await expect(promiseForNoInfuraIsUnblockedEvents).toBeFulfilled(); - await expect(promiseForInfuraIsBlocked).toBeFulfilled(); - }, - ); - }); - - it('checks the status of the previous network again and updates state accordingly', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: networkType, - }), - networkConfigurations: { - testNetworkConfiguration: { - id: 'testNetworkConfiguration', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'TEST', - }, - }, - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ - { - request: { - method: 'net_version', - }, - error: ethErrors.rpc.methodNotFound(), - }, - ]), - buildFakeProvider([ - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.setActiveNetwork('testNetworkConfiguration'); - expect(controller.store.getState().networkStatus).toBe( - 'unavailable', - ); - - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - operation: async () => { - await controller.rollbackToPreviousProvider(); - }, - }); - expect(controller.store.getState().networkStatus).toBe( - 'available', - ); - }, - ); - }); - - it('checks whether the previous network supports EIP-1559 again and updates state accordingly', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: networkType, - }), - networkConfigurations: { - testNetworkConfiguration: { - id: 'testNetworkConfiguration', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - ticker: 'TEST', - }, - }, - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: PRE_1559_BLOCK, - }, - }, - ]), - buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: POST_1559_BLOCK, - }, - }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - network: networkType, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.setActiveNetwork('testNetworkConfiguration'); - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: { - 1559: false, - }, - }); - - await waitForStateChanges({ - controller, - propertyPath: ['networkDetails'], - // rollbackToPreviousProvider clears networkDetails first, and - // then updates it to what we expect it to be - count: 2, - operation: async () => { - await controller.rollbackToPreviousProvider(); - }, - }); - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: { - 1559: true, - }, - }); - }, - ); - }); - }); - } - - describe(`if the previous provider configuration had a type of "rpc"`, () => { - it('emits networkWillChange', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: NetworkType.rpc, - }), - }, - }, - async ({ controller, messenger }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await controller.setProviderType(NetworkType.goerli); - - const networkWillChange = waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:networkWillChange', - operation: () => { - // Intentionally not awaited because we're capturing an event - // emitted partway through the operation - controller.rollbackToPreviousProvider(); - }, - }); - - await expect(networkWillChange).toBeFulfilled(); - }, - ); - }); - - it('emits networkDidChange', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: NetworkType.rpc, - }), - }, - }, - async ({ controller, messenger }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await controller.setProviderType(NetworkType.goerli); - - const networkDidChange = waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:networkDidChange', - operation: () => { - // Intentionally not awaited because we're capturing an event - // emitted partway through the operation - controller.rollbackToPreviousProvider(); - }, - }); - - await expect(networkDidChange).toBeFulfilled(); - }, - ); - }); - - it('overwrites the the current provider configuration with the previous provider configuration', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: NetworkType.rpc, - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - nickname: 'network', - ticker: 'TEST', - rpcPrefs: { - blockExplorerUrl: 'https://test-block-explorer.com', - }, - }), - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [buildFakeProvider(), buildFakeProvider()]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - network: NetworkType.goerli, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.setProviderType('goerli'); - expect(controller.store.getState().providerConfig).toStrictEqual({ - type: 'goerli', - rpcUrl: undefined, - chainId: toHex(5), - ticker: 'GoerliETH', - nickname: undefined, - rpcPrefs: { - blockExplorerUrl: 'https://goerli.etherscan.io', - }, - id: undefined, - }); - - await controller.rollbackToPreviousProvider(); - expect(controller.store.getState().providerConfig).toStrictEqual( - buildProviderConfig({ - type: 'rpc', - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - nickname: 'network', - ticker: 'TEST', - rpcPrefs: { - blockExplorerUrl: 'https://test-block-explorer.com', - }, - }), - ); - }, - ); - }); - - it('resets the network state to "unknown" before updating the provider', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: NetworkType.rpc, - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - }), - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - ]), - buildFakeProvider(), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - network: NetworkType.goerli, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.setProviderType('goerli'); - expect(controller.store.getState().networkStatus).toBe('available'); - - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - // We only care about the first state change, because it - // happens before networkDidChange - count: 1, - operation: () => { - // Intentionally not awaited because we want to check state - // while this operation is in-progress - controller.rollbackToPreviousProvider(); - }, - beforeResolving: () => { - expect(controller.store.getState().networkStatus).toBe( - 'unknown', - ); - }, - }); - }, - ); - }); - - it('clears EIP-1559 support for the network from state before updating the provider', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: NetworkType.rpc, - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - }), - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: POST_1559_BLOCK, - }, - }, - ]), - buildFakeProvider(), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - network: NetworkType.goerli, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.setProviderType('goerli'); - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: { - 1559: true, - }, - }); - - await waitForStateChanges({ - controller, - propertyPath: ['networkDetails'], - // We only care about the first state change, because it - // happens before networkDidChange - count: 1, - operation: () => { - // Intentionally not awaited because we want to check state - // while this operation is in-progress - controller.rollbackToPreviousProvider(); - }, - beforeResolving: () => { - expect( - controller.store.getState().networkDetails, - ).toStrictEqual({ - EIPS: {}, - }); - }, - }); - }, - ); - }); - - it('initializes a provider pointed to the given RPC URL', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: NetworkType.rpc, - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - }), - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider(), - buildFakeProvider([ - { - request: { - method: 'test', - }, - response: { - result: 'test response', - }, - }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - network: NetworkType.goerli, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.setProviderType('goerli'); - - await controller.rollbackToPreviousProvider(); - - const { provider } = controller.getProviderAndBlockTracker(); - assert(provider, 'Provider is somehow unset'); - const promisifiedSendAsync = promisify(provider.sendAsync).bind( - provider, - ); - const response = await promisifiedSendAsync({ - id: '1', - jsonrpc: '2.0', - method: 'test', - }); - expect(response.result).toBe('test response'); - }, - ); - }); - - it('replaces the provider object underlying the provider proxy without creating a new instance of the proxy itself', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: NetworkType.rpc, - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - }), - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [buildFakeProvider(), buildFakeProvider()]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - network: NetworkType.goerli, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.setProviderType('goerli'); - const { provider: providerBefore } = - controller.getProviderAndBlockTracker(); - - await controller.rollbackToPreviousProvider(); - - const { provider: providerAfter } = - controller.getProviderAndBlockTracker(); - expect(providerBefore).toBe(providerAfter); - }, - ); - }); - - it('emits infuraIsUnblocked', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: NetworkType.rpc, - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - }), - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller, messenger }) => { - const fakeProviders = [buildFakeProvider(), buildFakeProvider()]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - network: NetworkType.goerli, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.setProviderType('goerli'); - - const promiseForInfuraIsUnblocked = waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsUnblocked', - operation: async () => { - await controller.rollbackToPreviousProvider(); - }, - }); - - await expect(promiseForInfuraIsUnblocked).toBeFulfilled(); - }, - ); - }); - - it('checks the status of the previous network again and updates state accordingly', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: NetworkType.rpc, - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - }), - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ - { - request: { - method: 'net_version', - }, - error: ethErrors.rpc.methodNotFound(), - }, - ]), - buildFakeProvider([ - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - network: NetworkType.goerli, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.setProviderType('goerli'); - expect(controller.store.getState().networkStatus).toBe( - 'unavailable', - ); - - await controller.rollbackToPreviousProvider(); - expect(controller.store.getState().networkStatus).toBe('available'); - }, - ); - }); - - it('checks whether the previous network supports EIP-1559 again and updates state accordingly', async () => { - await withController( - { - state: { - providerConfig: buildProviderConfig({ - type: NetworkType.rpc, - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - }), - }, - infuraProjectId: 'some-infura-project-id', - }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: PRE_1559_BLOCK, - }, - }, - ]), - buildFakeProvider([ - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: POST_1559_BLOCK, - }, - }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - mockCreateNetworkClient() - .calledWith({ - network: NetworkType.goerli, - infuraProjectId: 'some-infura-project-id', - type: NetworkClientType.Infura, - }) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith({ - rpcUrl: 'https://mock-rpc-url', - chainId: toHex(1337), - type: NetworkClientType.Custom, - }) - .mockReturnValue(fakeNetworkClients[1]); - await controller.setProviderType('goerli'); - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: { - 1559: false, - }, - }); - - await controller.rollbackToPreviousProvider(); - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: { - 1559: true, - }, - }); - }, - ); - }); - }); - }); -}); - -/** - * Creates a mocked version of `createNetworkClient` where multiple mock - * invocations can be specified. A default implementation is provided so that if - * none of the actual invocations of the function match the mock invocations - * then an error will be thrown. - */ -function mockCreateNetworkClient() { - return when(createNetworkClientMock).mockImplementation((options) => { - const inspectedOptions = inspect(options, { depth: null, compact: true }); - const lines = [ - `No fake network client was specified for ${inspectedOptions}.`, - 'Make sure to mock this invocation of `createNetworkClient`.', - ]; - if ('infuraProjectId' in options) { - lines.push( - '(You might have forgotten to pass an `infuraProjectId` to `withController`.)', - ); - } - throw new Error(lines.join('\n')); - }); -} - -/** - * Test an operation that performs a `#switchNetwork` call with the given - * provider configuration. All effects of the `#switchNetwork` call should be - * covered by these tests. - * - * @param args - Arguments. - * @param args.expectedProviderConfig - The provider configuration that the - * operation is expected to set. - * @param args.initialState - The initial state of the network controller. - * @param args.operation - The operation to test. - */ -function refreshNetworkTests({ - expectedProviderConfig, - initialState, - operation, -}: { - expectedProviderConfig: ProviderConfiguration; - initialState?: Partial; - operation: (controller: NetworkController) => Promise; -}) { - it('emits networkWillChange', async () => { - await withController( - { - state: initialState, - }, - async ({ controller, messenger }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - - const networkWillChange = await waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:networkWillChange', - operation: () => { - // Intentionally not awaited because we're capturing an event - // emitted partway through the operation - operation(controller); - }, - }); - - await expect(networkWillChange).toBeTruthy(); - }, - ); - }); - - it('emits networkDidChange', async () => { - await withController( - { - state: initialState, - }, - async ({ controller, messenger }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - - const networkDidChange = await waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:networkDidChange', - operation: () => { - // Intentionally not awaited because we're capturing an event - // emitted partway through the operation - operation(controller); - }, - }); - - await expect(networkDidChange).toBeTruthy(); - }, - ); - }); - - it('clears network id from state', async () => { - await withController( - { - infuraProjectId: 'infura-project-id', - state: initialState, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - // Called during provider initialization - { - request: { - method: 'net_version', - }, - response: { - result: '1', - }, - }, - // Called during network lookup after resetting connection. - // Delayed to ensure that we can check the network id - // before this resolves. - { - delay: 1, - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: '0x1', - }, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - createNetworkClientMock.mockReturnValue(fakeNetworkClient); - await controller.initializeProvider(); - expect(controller.store.getState().networkId).toBe('1'); - - await waitForStateChanges({ - controller, - propertyPath: ['networkDetails'], - // We only care about the first state change, because it - // happens before the network lookup - count: 1, - operation: () => { - // Intentionally not awaited because we want to check state - // partway through the operation - operation(controller); - }, - }); - - expect(controller.store.getState().networkId).toBeNull(); - }, - ); - }); - - it('clears network status from state', async () => { - await withController( - { - infuraProjectId: 'infura-project-id', - state: initialState, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - // Called during provider initialization - { - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - // Called during network lookup after resetting connection. - // Delayed to ensure that we can check the network status - // before this resolves. - { - delay: 1, - request: { - method: 'net_version', - }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - delay: 1, - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - createNetworkClientMock.mockReturnValue(fakeNetworkClient); - await controller.initializeProvider(); - expect(controller.store.getState().networkStatus).toBe( - NetworkStatus.Available, - ); - - await waitForStateChanges({ - controller, - propertyPath: ['networkStatus'], - // We only care about the first state change, because it - // happens before the network lookup - count: 1, - operation: () => { - // Intentionally not awaited because we want to check state - // partway through the operation - operation(controller); - }, - }); - - expect(controller.store.getState().networkStatus).toBe( - NetworkStatus.Unknown, - ); - }, - ); - }); - - it('clears network details from state', async () => { - await withController( - { - infuraProjectId: 'infura-project-id', - state: initialState, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - // Called during provider initialization - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: '0x1', - }, - }, - // Called during network lookup after resetting connection. - // Delayed to ensure that we can check the network details - // before this resolves. - { - delay: 1, - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: '0x1', - }, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - createNetworkClientMock.mockReturnValue(fakeNetworkClient); - await controller.initializeProvider(); - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: { - 1559: false, - }, - }); - - await waitForStateChanges({ - controller, - propertyPath: ['networkDetails'], - // We only care about the first state change, because it - // happens before the network lookup - count: 1, - operation: () => { - // Intentionally not awaited because we want to check state - // partway through the operation - operation(controller); - }, - }); - - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: {}, - }); - }, - ); - }); - - if (expectedProviderConfig.type === NetworkType.rpc) { - it('sets the provider to a custom RPC provider initialized with the RPC target and chain ID', async () => { - await withController( - { - infuraProjectId: 'infura-project-id', - state: initialState, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'eth_chainId', - }, - response: { - result: toHex(111), - }, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - createNetworkClientMock.mockReturnValue(fakeNetworkClient); - - await operation(controller); - - expect(createNetworkClientMock).toHaveBeenCalledWith({ - chainId: expectedProviderConfig.chainId, - rpcUrl: expectedProviderConfig.rpcUrl, - type: NetworkClientType.Custom, - }); - const { provider } = controller.getProviderAndBlockTracker(); - assert(provider); - const promisifiedSendAsync = promisify(provider.sendAsync).bind( - provider, - ); - const chainIdResult = await promisifiedSendAsync({ - id: 1, - jsonrpc: '2.0', - method: 'eth_chainId', - params: [], - }); - expect(chainIdResult.result).toBe(toHex(111)); - }, - ); - }); - } else { - it(`sets the provider to an Infura provider pointed to ${expectedProviderConfig.type}`, async () => { - await withController( - { - infuraProjectId: 'infura-project-id', - state: initialState, - }, - async ({ controller }) => { - const fakeProvider = buildFakeProvider([ - { - request: { - method: 'eth_chainId', - }, - response: { - result: toHex(1337), - }, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - createNetworkClientMock.mockReturnValue(fakeNetworkClient); - - await operation(controller); - - expect(createNetworkClientMock).toHaveBeenCalledWith({ - network: expectedProviderConfig.type, - infuraProjectId: 'infura-project-id', - type: NetworkClientType.Infura, - }); - const { provider } = controller.getProviderAndBlockTracker(); - assert(provider); - const promisifiedSendAsync = promisify(provider.sendAsync).bind( - provider, - ); - const chainIdResult = await promisifiedSendAsync({ - id: 1, - jsonrpc: '2.0', - method: 'eth_chainId', - params: [], - }); - expect(chainIdResult.result).toBe(toHex(1337)); - }, - ); - }); - } - - it('replaces the provider object underlying the provider proxy without creating a new instance of the proxy itself', async () => { - await withController( - { - infuraProjectId: 'infura-project-id', - state: initialState, - }, - async ({ controller }) => { - const fakeProviders = [buildFakeProvider(), buildFakeProvider()]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - const providerType = controller.store.getState().providerConfig.type; - const initializationNetworkClientOptions: Parameters< - typeof createNetworkClient - >[0] = - providerType === NetworkType.rpc - ? { - chainId: controller.store.getState().providerConfig.chainId, - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - rpcUrl: controller.store.getState().providerConfig.rpcUrl!, - type: NetworkClientType.Custom, - } - : { - network: providerType, - infuraProjectId: 'infura-project-id', - type: NetworkClientType.Infura, - }; - const operationNetworkClientOptions: Parameters< - typeof createNetworkClient - >[0] = - expectedProviderConfig.type === NetworkType.rpc - ? { - chainId: toHex(expectedProviderConfig.chainId), - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - rpcUrl: expectedProviderConfig.rpcUrl!, - type: NetworkClientType.Custom, - } - : { - network: expectedProviderConfig.type, - infuraProjectId: 'infura-project-id', - type: NetworkClientType.Infura, - }; - mockCreateNetworkClient() - .calledWith(initializationNetworkClientOptions) - .mockReturnValue(fakeNetworkClients[0]) - .calledWith(operationNetworkClientOptions) - .mockReturnValue(fakeNetworkClients[1]); - await controller.initializeProvider(); - const { provider: providerBefore } = - controller.getProviderAndBlockTracker(); - - await operation(controller); - - const { provider: providerAfter } = - controller.getProviderAndBlockTracker(); - expect(providerBefore).toBe(providerAfter); - }, - ); - }); - - lookupNetworkTests({ expectedProviderConfig, initialState, operation }); -} - -/** - * Test an operation that performs a `lookupNetwork` call with the given - * provider configuration. All effects of the `lookupNetwork` call should be - * covered by these tests. - * - * @param args - Arguments. - * @param args.expectedProviderConfig - The provider configuration that the - * operation is expected to set. - * @param args.initialState - The initial state of the network controller. - * @param args.operation - The operation to test. - */ -function lookupNetworkTests({ - expectedProviderConfig, - initialState, - operation, -}: { - expectedProviderConfig: ProviderConfiguration; - initialState?: Partial; - operation: (controller: NetworkController) => Promise; -}) { - describe('if the network ID and network details requests resolve successfully', () => { - const validNetworkIds = [12345, '12345', toHex(12345)]; - for (const networkId of validNetworkIds) { - describe(`with a network id of '${networkId}'`, () => { - describe('if the current network is different from the network in state', () => { - it('updates the network in state to match', async () => { - await withController( - { - state: initialState, - }, - async ({ controller }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { method: 'net_version' }, - response: { result: networkId }, - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - await operation(controller); - - expect(controller.store.getState().networkId).toBe('12345'); - }, - ); - }); - }); - - describe('if the version of the current network is the same as that in state', () => { - it('does not change network ID in state', async () => { - await withController( - { - state: initialState, - }, - async ({ controller }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { method: 'net_version' }, - response: { result: networkId }, - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - await operation(controller); - - await expect(controller.store.getState().networkId).toBe( - '12345', - ); - }, - ); - }); - - it('updates the network details', async () => { - await withController( - { - state: initialState, - }, - async ({ controller }) => { - await setFakeProvider(controller, { - stubs: [ - // Called during provider initialization - { - request: { - method: 'net_version', - }, - response: { result: networkId }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: PRE_1559_BLOCK, - }, - }, - // Called via `lookupNetwork` directly - { - request: { - method: 'net_version', - }, - response: { result: networkId }, - }, - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: POST_1559_BLOCK, - }, - }, - ], - }); - - await operation(controller); - - await expect( - controller.store.getState().networkDetails, - ).toStrictEqual({ - EIPS: { - 1559: true, - }, - }); - }, - ); - }); - }); - }); - } - - describe('if the network details of the current network are different from the network details in state', () => { - it('updates the network in state to match', async () => { - await withController( - { - state: { - ...initialState, - networkDetails: { - EIPS: { - 1559: false, - }, - }, - }, - }, - async ({ controller }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - response: { - result: { - baseFeePerGas: '0x1', - }, - }, - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - await operation(controller); - - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: { - 1559: true, - }, - }); - }, - ); - }); - }); - - describe('if the network details of the current network are the same as the network details in state', () => { - it('does not change network details in state', async () => { - await withController( - { - state: { - ...initialState, - networkDetails: { - EIPS: { - 1559: true, - }, - }, - }, - }, - async ({ controller }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - response: { - result: { - baseFeePerGas: '0x1', - }, - }, - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - await operation(controller); - - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: { - 1559: true, - }, - }); - }, - ); - }); - }); - - it('emits infuraIsUnblocked', async () => { - await withController( - { - state: initialState, - }, - async ({ controller, messenger }) => { - await setFakeProvider(controller, { - stubLookupNetworkWhileSetting: true, - }); - - const payloads = await waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsUnblocked', - operation: async () => { - await operation(controller); - }, - }); - - expect(payloads).toBeTruthy(); - }, - ); - }); - - it('does not emit infuraIsBlocked', async () => { - await withController( - { - state: initialState, - }, - async ({ controller, messenger }) => { - await setFakeProvider(controller, { - stubLookupNetworkWhileSetting: true, - }); - - const payloads = await waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsBlocked', - count: 0, - operation: async () => { - await operation(controller); - }, - }); - - expect(payloads).toBeTruthy(); - }, - ); - }); - }); - - describe('if an RPC error is encountered while retrieving the version of the current network', () => { - it('updates the network in state to "unavailable"', async () => { - await withController( - { - state: initialState, - }, - async ({ controller }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { method: 'net_version' }, - error: ethErrors.rpc.limitExceeded('some error'), - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - await operation(controller); - - expect(controller.store.getState().networkStatus).toBe('unavailable'); - }, - ); - }); - - it('resets the network details in state', async () => { - await withController( - { - state: initialState, - }, - async ({ controller }) => { - await setFakeProvider(controller, { - stubs: [ - // Called during provider initialization - { - request: { method: 'net_version' }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - response: { - result: PRE_1559_BLOCK, - }, - }, - // Called when calling the operation directly - { - request: { method: 'net_version' }, - error: ethErrors.rpc.limitExceeded('some error'), - }, - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - response: { - result: POST_1559_BLOCK, - }, - }, - ], - }); - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: { - 1559: false, - }, - }); - - await operation(controller); - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: {}, - }); - }, - ); - }); - - if (expectedProviderConfig.type === NetworkType.rpc) { - it('emits infuraIsUnblocked', async () => { - await withController( - { - state: initialState, - }, - async ({ controller, messenger }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { method: 'net_version' }, - error: ethErrors.rpc.limitExceeded('some error'), - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - const payloads = await waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsUnblocked', - operation: async () => { - await operation(controller); - }, - }); - - expect(payloads).toBeTruthy(); - }, - ); - }); - } else { - it('does not emit infuraIsUnblocked', async () => { - await withController( - { - state: initialState, - }, - async ({ controller, messenger }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { method: 'net_version' }, - error: ethErrors.rpc.limitExceeded('some error'), - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - const payloads = await waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsUnblocked', - count: 0, - operation: async () => { - await operation(controller); - }, - }); - - expect(payloads).toBeTruthy(); - }, - ); - }); - } - - it('does not emit infuraIsBlocked', async () => { - await withController( - { - state: initialState, - }, - async ({ controller, messenger }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { method: 'net_version' }, - error: ethErrors.rpc.limitExceeded('some error'), - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - const payloads = await waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsBlocked', - count: 0, - operation: async () => { - await operation(controller); - }, - }); - - expect(payloads).toBeTruthy(); - }, - ); - }); - }); - - describe('if a country blocked error is encountered while retrieving the version of the current network', () => { - if (expectedProviderConfig.type === NetworkType.rpc) { - it('updates the network in state to "unknown"', async () => { - await withController( - { - state: initialState, - }, - async ({ controller }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { method: 'net_version' }, - error: BLOCKED_INFURA_JSON_RPC_ERROR, - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - await operation(controller); - - expect(controller.store.getState().networkStatus).toBe('unknown'); - }, - ); - }); - - it('emits infuraIsUnblocked', async () => { - await withController( - { - state: initialState, - }, - async ({ controller, messenger }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { method: 'net_version' }, - error: BLOCKED_INFURA_JSON_RPC_ERROR, - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - const payloads = await waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsUnblocked', - operation: async () => { - await operation(controller); - }, - }); - - expect(payloads).toBeTruthy(); - }, - ); - }); - - it('does not emit infuraIsBlocked', async () => { - await withController( - { - state: initialState, - }, - async ({ controller, messenger }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { method: 'net_version' }, - error: BLOCKED_INFURA_JSON_RPC_ERROR, - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - const payloads = await waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsBlocked', - count: 0, - operation: async () => { - await operation(controller); - }, - }); - - expect(payloads).toBeTruthy(); - }, - ); - }); - } else { - it('updates the network in state to "blocked"', async () => { - await withController( - { - state: initialState, - }, - async ({ controller }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { method: 'net_version' }, - error: BLOCKED_INFURA_JSON_RPC_ERROR, - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - await operation(controller); - - expect(controller.store.getState().networkStatus).toBe('blocked'); - }, - ); - }); - - it('does not emit infuraIsUnblocked', async () => { - await withController( - { - state: initialState, - }, - async ({ controller, messenger }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { method: 'net_version' }, - error: BLOCKED_INFURA_JSON_RPC_ERROR, - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - const payloads = await waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsUnblocked', - count: 0, - operation: async () => { - await operation(controller); - }, - }); - - expect(payloads).toBeTruthy(); - }, - ); - }); - - it('emits infuraIsBlocked', async () => { - await withController( - { - state: initialState, - }, - async ({ controller, messenger }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { method: 'net_version' }, - error: BLOCKED_INFURA_JSON_RPC_ERROR, - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - const payloads = await waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsBlocked', - operation: async () => { - await operation(controller); - }, - }); - - expect(payloads).toBeTruthy(); - }, - ); - }); - } - - it('resets the network details in state', async () => { - await withController( - { - state: initialState, - }, - async ({ controller }) => { - await setFakeProvider(controller, { - stubs: [ - // Called during provider initialization - { - request: { method: 'net_version' }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - response: { - result: PRE_1559_BLOCK, - }, - }, - // Called when calling the operation directly - { - request: { method: 'net_version' }, - error: BLOCKED_INFURA_JSON_RPC_ERROR, - }, - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - response: { - result: POST_1559_BLOCK, - }, - }, - ], - }); - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: { - 1559: false, - }, - }); - - await operation(controller); - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: {}, - }); - }, - ); - }); - }); - - describe('if an internal error is encountered while retrieving the version of the current network', () => { - it('updates the network in state to "unknown"', async () => { - await withController( - { - state: initialState, - }, - async ({ controller }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { method: 'net_version' }, - error: ethErrors.rpc.internal('some error'), - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - await operation(controller); - - expect(controller.store.getState().networkStatus).toBe('unknown'); - }, - ); - }); - - it('resets the network details in state', async () => { - await withController( - { - state: initialState, - }, - async ({ controller }) => { - await setFakeProvider(controller, { - stubs: [ - // Called during provider initialization - { - request: { method: 'net_version' }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - response: { - result: PRE_1559_BLOCK, - }, - }, - // Called when calling the operation directly - { - request: { method: 'net_version' }, - error: GENERIC_JSON_RPC_ERROR, - }, - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - response: { - result: POST_1559_BLOCK, - }, - }, - ], - }); - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: { - 1559: false, - }, - }); - - await operation(controller); - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: {}, - }); - }, - ); - }); - - if (expectedProviderConfig.type === NetworkType.rpc) { - it('emits infuraIsUnblocked', async () => { - await withController( - { - state: initialState, - }, - async ({ controller, messenger }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { method: 'net_version' }, - error: ethErrors.rpc.internal('some error'), - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - const payloads = await waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsUnblocked', - operation: async () => { - await operation(controller); - }, - }); - - expect(payloads).toBeTruthy(); - }, - ); - }); - } else { - it('does not emit infuraIsUnblocked', async () => { - await withController( - { - state: initialState, - }, - async ({ controller, messenger }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { method: 'net_version' }, - error: ethErrors.rpc.internal('some error'), - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - const payloads = await waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsUnblocked', - count: 0, - operation: async () => { - await operation(controller); - }, - }); - - expect(payloads).toBeTruthy(); - }, - ); - }); - } - - it('does not emit infuraIsBlocked', async () => { - await withController( - { - state: initialState, - }, - async ({ controller, messenger }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { method: 'net_version' }, - error: ethErrors.rpc.internal('some error'), - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - const payloads = await waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsBlocked', - count: 0, - operation: async () => { - await operation(controller); - }, - }); - - expect(payloads).toBeTruthy(); - }, - ); - }); - }); - - describe('if an invalid network ID is returned', () => { - it('updates the network in state to "unknown"', async () => { - await withController( - { - state: initialState, - }, - async ({ controller }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { method: 'net_version' }, - response: { result: 'invalid' }, - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - await operation(controller); - - expect(controller.store.getState().networkStatus).toBe('unknown'); - }, - ); - }); - - it('resets the network details in state', async () => { - await withController( - { - state: initialState, - }, - async ({ controller }) => { - await setFakeProvider(controller, { - stubs: [ - // Called during provider initialization - { - request: { method: 'net_version' }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - }, - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - response: { - result: PRE_1559_BLOCK, - }, - }, - // Called when calling the operation directly - { - request: { method: 'net_version' }, - response: { result: 'invalid' }, - }, - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - response: { - result: POST_1559_BLOCK, - }, - }, - ], - }); - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: { - 1559: false, - }, - }); - - await operation(controller); - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: {}, - }); - }, - ); - }); - - if (expectedProviderConfig.type === NetworkType.rpc) { - it('emits infuraIsUnblocked', async () => { - await withController( - { - state: initialState, - }, - async ({ controller, messenger }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { method: 'net_version' }, - response: { result: 'invalid' }, - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - const payloads = await waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsUnblocked', - operation: async () => { - await operation(controller); - }, - }); - - expect(payloads).toBeTruthy(); - }, - ); - }); - } else { - it('does not emit infuraIsUnblocked', async () => { - await withController( - { - state: initialState, - }, - async ({ controller, messenger }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { method: 'net_version' }, - response: { result: 'invalid' }, - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - const payloads = await waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsUnblocked', - count: 0, - operation: async () => { - await operation(controller); - }, - }); - - expect(payloads).toBeTruthy(); - }, - ); - }); - } - - it('does not emit infuraIsBlocked', async () => { - await withController( - { - state: initialState, - }, - async ({ controller, messenger }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { method: 'net_version' }, - response: { result: 'invalid' }, - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - const payloads = await waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsBlocked', - count: 0, - operation: async () => { - await operation(controller); - }, - }); - - expect(payloads).toBeTruthy(); - }, - ); - }); - }); - - describe('if an RPC error is encountered while retrieving the network details of the current network', () => { - it('updates the network in state to "unavailable"', async () => { - await withController( - { - state: initialState, - }, - async ({ controller }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - error: ethErrors.rpc.limitExceeded('some error'), - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - await operation(controller); - - expect(controller.store.getState().networkStatus).toBe('unavailable'); - }, - ); - }); - - it('resets the network details in state', async () => { - await withController( - { - state: initialState, - }, - async ({ controller }) => { - await setFakeProvider(controller, { - stubs: [ - // Called during provider initialization - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - response: { - result: PRE_1559_BLOCK, - }, - }, - // Called when calling the operation directly - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - error: ethErrors.rpc.limitExceeded('some error'), - }, - ], - }); - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: { - 1559: false, - }, - }); - - await operation(controller); - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: {}, - }); - }, - ); - }); - - if (expectedProviderConfig.type === NetworkType.rpc) { - it('emits infuraIsUnblocked', async () => { - await withController( - { - state: initialState, - }, - async ({ controller, messenger }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - error: ethErrors.rpc.limitExceeded('some error'), - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - const payloads = await waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsUnblocked', - operation: async () => { - await operation(controller); - }, - }); - - expect(payloads).toBeTruthy(); - }, - ); - }); - } else { - it('does not emit infuraIsUnblocked', async () => { - await withController( - { - state: initialState, - }, - async ({ controller, messenger }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - error: ethErrors.rpc.limitExceeded('some error'), - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - const payloads = await waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsUnblocked', - count: 0, - operation: async () => { - await operation(controller); - }, - }); - - expect(payloads).toBeTruthy(); - }, - ); - }); - } - - it('does not emit infuraIsBlocked', async () => { - await withController( - { - state: initialState, - }, - async ({ controller, messenger }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - error: ethErrors.rpc.limitExceeded('some error'), - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - const payloads = await waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsBlocked', - count: 0, - operation: async () => { - await operation(controller); - }, - }); - - expect(payloads).toBeTruthy(); - }, - ); - }); - }); - - describe('if a country blocked error is encountered while retrieving the network details of the current network', () => { - if (expectedProviderConfig.type === NetworkType.rpc) { - it('updates the network in state to "unknown"', async () => { - await withController( - { - state: initialState, - }, - async ({ controller }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - error: BLOCKED_INFURA_JSON_RPC_ERROR, - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - await operation(controller); - - expect(controller.store.getState().networkStatus).toBe('unknown'); - }, - ); - }); - - it('emits infuraIsUnblocked', async () => { - await withController( - { - state: initialState, - }, - async ({ controller, messenger }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - error: BLOCKED_INFURA_JSON_RPC_ERROR, - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - const payloads = await waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsUnblocked', - operation: async () => { - await operation(controller); - }, - }); - - expect(payloads).toBeTruthy(); - }, - ); - }); - - it('does not emit infuraIsBlocked', async () => { - await withController( - { - state: initialState, - }, - async ({ controller, messenger }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - error: BLOCKED_INFURA_JSON_RPC_ERROR, - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - const payloads = await waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsBlocked', - count: 0, - operation: async () => { - await operation(controller); - }, - }); - - expect(payloads).toBeTruthy(); - }, - ); - }); - } else { - it('updates the network in state to "blocked"', async () => { - await withController( - { - state: initialState, - }, - async ({ controller }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - error: BLOCKED_INFURA_JSON_RPC_ERROR, - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - await operation(controller); - - expect(controller.store.getState().networkStatus).toBe('blocked'); - }, - ); - }); - - it('does not emit infuraIsUnblocked', async () => { - await withController( - { - state: initialState, - }, - async ({ controller, messenger }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - error: BLOCKED_INFURA_JSON_RPC_ERROR, - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - const payloads = await waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsUnblocked', - count: 0, - operation: async () => { - await operation(controller); - }, - }); - - expect(payloads).toBeTruthy(); - }, - ); - }); - - it('emits infuraIsBlocked', async () => { - await withController( - { - state: initialState, - }, - async ({ controller, messenger }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - error: BLOCKED_INFURA_JSON_RPC_ERROR, - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - const payloads = await waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsBlocked', - operation: async () => { - await operation(controller); - }, - }); - - expect(payloads).toBeTruthy(); - }, - ); - }); - } - - it('resets the network details in state', async () => { - await withController( - { - state: initialState, - }, - async ({ controller }) => { - await setFakeProvider(controller, { - stubs: [ - // Called during provider initialization - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - response: { - result: PRE_1559_BLOCK, - }, - }, - // Called when calling the operation directly - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - error: BLOCKED_INFURA_JSON_RPC_ERROR, - }, - ], - }); - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: { - 1559: false, - }, - }); - - await operation(controller); - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: {}, - }); - }, - ); - }); - }); - - describe('if an internal error is encountered while retrieving the network details of the current network', () => { - it('updates the network in state to "unknown"', async () => { - await withController( - { - state: initialState, - }, - async ({ controller }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - error: ethErrors.rpc.internal('some error'), - }, - ], - }); - - await operation(controller); - - expect(controller.store.getState().networkStatus).toBe('unknown'); - }, - ); - }); - - it('resets the network details in state', async () => { - await withController( - { - state: initialState, - }, - async ({ controller }) => { - await setFakeProvider(controller, { - stubs: [ - // Called during provider initialization - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - response: { - result: PRE_1559_BLOCK, - }, - }, - // Called when calling the operation directly - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - error: GENERIC_JSON_RPC_ERROR, - }, - ], - }); - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: { - 1559: false, - }, - }); - - await operation(controller); - expect(controller.store.getState().networkDetails).toStrictEqual({ - EIPS: {}, - }); - }, - ); - }); - - if (expectedProviderConfig.type === NetworkType.rpc) { - it('emits infuraIsUnblocked', async () => { - await withController( - { - state: initialState, - }, - async ({ controller, messenger }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - error: ethErrors.rpc.internal('some error'), - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - const payloads = await waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsUnblocked', - operation: async () => { - await operation(controller); - }, - }); - - expect(payloads).toBeTruthy(); - }, - ); - }); - } else { - it('does not emit infuraIsUnblocked', async () => { - await withController( - { - state: initialState, - }, - async ({ controller, messenger }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - error: ethErrors.rpc.internal('some error'), - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - const payloads = await waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsUnblocked', - count: 0, - operation: async () => { - await operation(controller); - }, - }); - - expect(payloads).toBeTruthy(); - }, - ); - }); - } - - it('does not emit infuraIsBlocked', async () => { - await withController( - { - state: initialState, - }, - async ({ controller, messenger }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - error: ethErrors.rpc.internal('some error'), - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - const payloads = await waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsBlocked', - count: 0, - operation: async () => { - await operation(controller); - }, - }); - - expect(payloads).toBeTruthy(); - }, - ); - }); - }); -} - -/** - * Builds a controller messenger. - * - * @returns The controller messenger. - */ -function buildMessenger() { - return new ControllerMessenger< - NetworkControllerAction, - NetworkControllerEvent - >(); -} - -/** - * Build a restricted controller messenger for the network controller. - * - * @param messenger - A controller messenger. - * @returns The network controller restricted messenger. - */ -function buildNetworkControllerMessenger(messenger = buildMessenger()) { - return messenger.getRestricted({ - name: 'NetworkController', - allowedActions: [ - 'NetworkController:getProviderConfig', - 'NetworkController:getEthQuery', - ], - allowedEvents: [ - 'NetworkController:networkDidChange', - 'NetworkController:networkWillChange', - 'NetworkController:infuraIsBlocked', - 'NetworkController:infuraIsUnblocked', - ], - }); -} - -/** - * The callback that `withController` takes. - */ -type WithControllerCallback = ({ - controller, -}: { - controller: NetworkController; - messenger: ControllerMessenger< - NetworkControllerAction, - NetworkControllerEvent - >; -}) => Promise | ReturnValue; - -/** - * A partial form of the options that `NetworkController` takes. - */ -type WithControllerOptions = Partial; - -/** - * The arguments that `withController` takes. - */ -type WithControllerArgs = - | [WithControllerCallback] - | [ - Omit, - WithControllerCallback, - ]; - -/** - * Builds a controller based on the given options, and calls the given function - * with that controller. - * - * @param args - Either a function, or an options bag + a function. The options - * bag is equivalent to the options that NetworkController takes (although - * `messenger` and `infuraProjectId` are filled in if not given); the function - * will be called with the built controller. - * @returns Whatever the callback returns. - */ -async function withController( - ...args: WithControllerArgs -): Promise { - const [{ ...rest }, fn] = args.length === 2 ? args : [{}, args[0]]; - const messenger = buildMessenger(); - const restrictedMessenger = buildNetworkControllerMessenger(messenger); - const controller = new NetworkController({ - messenger: restrictedMessenger, - trackMetaMetricsEvent: jest.fn(), - infuraProjectId: 'infura-project-id', - ...rest, - }); - try { - return await fn({ controller, messenger }); - } finally { - await controller.destroy(); - } -} - -/** - * Builds a complete ProviderConfig object, filling in values that are not - * provided with defaults. - * - * @param config - An incomplete ProviderConfig object. - * @returns The complete ProviderConfig object. - */ -function buildProviderConfig( - config: Partial = {}, -): ProviderConfiguration { - if (config.type && config.type !== NetworkType.rpc) { - const networkConfig = INFURA_NETWORKS.find( - ({ networkType }) => networkType === config.type, - ); - if (!networkConfig) { - throw new Error(`Invalid type: ${config.type}`); - } - return { - ...networkConfig, - // This is redundant with the spread operation below, but this was - // required for TypeScript to understand that this property was set to an - // Infura type. - type: config.type, - ...config, - }; - } - return { - type: NetworkType.rpc, - chainId: '0x42', - nickname: undefined, - rpcUrl: 'http://doesntmatter.com', - ...config, - }; -} - -/** - * Builds an object that `createInfuraProvider` or `createJsonRpcClient` returns. - * - * @param provider - provider to use if you dont want the defaults - * @returns The object. - */ -function buildFakeClient( - provider: SafeEventEmitterProvider = buildFakeProvider(), -) { - return { - provider, - blockTracker: new FakeBlockTracker({ provider }), - }; -} - -/** - * Builds fake provider engine object that `createMetamaskProvider` returns, - * with canned responses optionally provided for certain RPC methods. - * - * @param stubs - The list of RPC methods you want to stub along with their - * responses. - * @returns The object. - */ -function buildFakeProvider(stubs: FakeProviderStub[] = []) { - const completeStubs = stubs.slice(); - if (!stubs.some((stub) => stub.request.method === 'eth_getBlockByNumber')) { - completeStubs.unshift({ - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - discardAfterMatching: false, - }); - } - if (!stubs.some((stub) => stub.request.method === 'net_version')) { - completeStubs.unshift({ - request: { method: 'net_version' }, - response: SUCCESSFUL_NET_VERSION_RESPONSE, - discardAfterMatching: false, - }); - } - return new FakeProvider({ stubs: completeStubs }); -} - -/** - * Asks the controller to set the provider in the simplest way, stubbing the - * provider appropriately so as not to cause any errors to be thrown. This is - * useful in tests where it doesn't matter how the provider gets set, just that - * it does. Canned responses may be optionally provided for certain RPC methods - * on the provider. - * - * @param controller - The network controller. - * @param options - Additional options. - * @param options.stubs - The set of RPC methods you want to stub on the - * provider along with their responses. - * @param options.stubLookupNetworkWhileSetting - Whether to stub the call to - * `lookupNetwork` that happens when the provider is set. This option is useful - * in tests that need a provider to get set but also call `lookupNetwork` on - * their own. In this case, since the `providerConfig` setter already calls - * `lookupNetwork` once, and since `lookupNetwork` is called out of band, the - * test may run with unexpected results. By stubbing `lookupNetwork` before - * setting the provider, the test is free to explicitly call it. - * @returns The set provider. - */ -async function setFakeProvider( - controller: NetworkController, - { - stubs = [], - stubLookupNetworkWhileSetting = false, - }: { - stubs?: FakeProviderStub[]; - stubLookupNetworkWhileSetting?: boolean; - } = {}, -): Promise { - const fakeProvider = buildFakeProvider(stubs); - const fakeNetworkClient = buildFakeClient(fakeProvider); - createNetworkClientMock.mockReturnValue(fakeNetworkClient); - const lookupNetworkMock = jest.spyOn(controller, 'lookupNetwork'); - - if (stubLookupNetworkWhileSetting) { - lookupNetworkMock.mockResolvedValue(undefined); - } - - await controller.initializeProvider(); - assert(controller.getProviderAndBlockTracker().provider); - - if (stubLookupNetworkWhileSetting) { - lookupNetworkMock.mockRestore(); - } -} - -/** - * Waits for changes to the primary observable store of a controller to occur - * before proceeding. May be called with a function, in which case waiting will - * occur after the function is called; or may be called standalone if you want - * to assert that no state changes occurred. - * - * @param args - The arguments. - * @param args.controller - The network controller. - * @param args.propertyPath - The path of the property you expect the state - * changes to concern. - * @param args.count - The number of events you expect to occur. If null, this - * function will wait until no events have occurred in `wait` number of - * milliseconds. Default: 1. - * @param args.duration - The amount of time in milliseconds to wait for the - * expected number of filtered state changes to occur before resolving the - * promise that this function returns (default: 150). - * @param args.operation - A function to run that will presumably produce the - * state changes in question. - * @param args.beforeResolving - In some tests, state updates happen so fast, we - * need to make an assertion immediately after the event in question occurs. - * However, if we wait until the promise this function returns resolves to do - * so, some other state update to the same - * property may have happened. This option allows you to make an assertion - * _before_ the promise resolves. This has the added benefit of allowing you to - * maintain the "arrange, act, assert" ordering in your test, meaning that you - * can still call the method that kicks off the event and then make the - * assertion afterward instead of the other way around. - * @returns A promise that resolves to an array of state objects (that is, the - * contents of the store) when the specified number of filtered state changes - * have occurred, or all of them if no number has been specified. - */ -async function waitForStateChanges({ - controller, - propertyPath, - count: expectedInterestingStateCount = 1, - duration: timeBeforeAssumingNoMoreStateChanges = 150, - operation = () => { - // do nothing - }, - beforeResolving = async () => { - // do nothing - }, -}: { - controller: NetworkController; - propertyPath?: string[]; - count?: number | null; - duration?: number; - operation?: () => void | Promise; - beforeResolving?: () => void | Promise; -}) { - const initialState = { ...controller.store.getState() }; - let isTimerRunning = false; - - const promiseForStateChanges = new Promise((resolve, reject) => { - // We need to declare this variable first, then assign it later, so that - // ESLint won't complain that resetTimer is referring to this variable - // before it's declared. And we need to use let so that we can assign it - // below. - /* eslint-disable-next-line prefer-const */ - let eventListener: (...args: any[]) => void; - let timer: NodeJS.Timeout | undefined; - const allStates: NetworkControllerState[] = []; - const interestingStates: NetworkControllerState[] = []; - - const stopTimer = () => { - if (timer) { - clearTimeout(timer); - } - isTimerRunning = false; - }; - - async function end() { - stopTimer(); - - controller.store.unsubscribe(eventListener); - - await beforeResolving(); - - const shouldEnd = - expectedInterestingStateCount === null - ? interestingStates.length > 0 - : interestingStates.length === expectedInterestingStateCount; - - if (shouldEnd) { - resolve(interestingStates); - } else { - // Using a string instead of an Error leads to better backtraces. - /* eslint-disable-next-line prefer-promise-reject-errors */ - const expectedInterestingStateCountFragment = - expectedInterestingStateCount === null - ? 'any number of' - : expectedInterestingStateCount; - const propertyPathFragment = - propertyPath === undefined ? '' : ` on \`${propertyPath.join('.')}\``; - const actualInterestingStateCountFragment = - expectedInterestingStateCount === null - ? 'none' - : interestingStates.length; - const primaryMessage = `Expected to receive ${expectedInterestingStateCountFragment} state change(s)${propertyPathFragment}, but received ${actualInterestingStateCountFragment} after ${timeBeforeAssumingNoMoreStateChanges}ms.`; - reject( - [ - primaryMessage, - 'Initial state:', - inspect(initialState, { depth: null }), - 'All state changes (without filtering):', - inspect(allStates, { depth: null }), - 'Filtered state changes:', - inspect(interestingStates, { depth: null }), - ].join('\n\n'), - ); - } - } - - const resetTimer = () => { - stopTimer(); - timer = setTimeout(() => { - if (isTimerRunning) { - end(); - } - }, timeBeforeAssumingNoMoreStateChanges); - isTimerRunning = true; - }; - - eventListener = (newState) => { - const isInteresting = - propertyPath === undefined - ? true - : isStateChangeInteresting( - newState, - allStates.length > 0 - ? allStates[allStates.length - 1] - : initialState, - propertyPath, - ); - - allStates.push({ ...newState }); - - if (isInteresting) { - interestingStates.push(newState); - if (interestingStates.length === expectedInterestingStateCount) { - end(); - } else { - resetTimer(); - } - } - }; - - controller.store.subscribe(eventListener); - resetTimer(); - }); - - await operation(); - - return await promiseForStateChanges; -} - -/** - * Waits for controller events to be emitted before proceeding. - * - * @param args - The arguments to this function. - * @param args.messenger - The messenger suited for NetworkController. - * @param args.eventType - The type of NetworkController event you want to wait - * for. - * @param args.count - The number of events you expect to occur (default: 1). - * @param args.filter - A function used to discard events that are not of - * interest. - * @param args.wait - The amount of time in milliseconds to wait for the - * expected number of filtered events to occur before resolving the promise that - * this function returns (default: 150). - * @param args.operation - A function to run that will presumably produce the - * events in question. - * @param args.beforeResolving - In some tests, state updates happen so fast, we - * need to make an assertion immediately after the event in question occurs. - * However, if we wait until the promise this function returns resolves to do - * so, some other state update to the same - * property may have happened. This option allows you to make an assertion - * _before_ the promise resolves. This has the added benefit of allowing you to - * maintain the "arrange, act, assert" ordering in your test, meaning that you - * can still call the method that kicks off the event and then make the - * assertion afterward instead of the other way around. - * @returns A promise that resolves to the list of payloads for the set of - * events, optionally filtered, when a specific number of them have occurred. - */ -async function waitForPublishedEvents({ - messenger, - eventType, - count: expectedNumberOfEvents = 1, - filter: isEventPayloadInteresting = () => true, - wait: timeBeforeAssumingNoMoreEvents = 150, - operation = () => { - // do nothing - }, - beforeResolving = async () => { - // do nothing - }, -}: { - messenger: ControllerMessenger; - eventType: E['type']; - count?: number; - filter?: (payload: E['payload']) => boolean; - wait?: number; - operation?: () => void | Promise; - beforeResolving?: () => void | Promise; -}): Promise { - const promiseForEventPayloads = new Promise( - (resolve, reject) => { - let timer: NodeJS.Timeout | undefined; - const allEventPayloads: E['payload'][] = []; - const interestingEventPayloads: E['payload'][] = []; - let alreadyEnded = false; - - // We're using `any` here because there seems to be some mismatch between - // the signature of `subscribe` and the way that we're using it. Try - // changing `any` to either `((...args: E['payload']) => void)` or - // `ExtractEventHandler` to see the issue. - const eventListener: any = (...payload: E['payload']) => { - allEventPayloads.push(payload); - - if (isEventPayloadInteresting(payload)) { - interestingEventPayloads.push(payload); - if (interestingEventPayloads.length === expectedNumberOfEvents) { - stopTimer(); - end(); - } else { - resetTimer(); - } - } - }; - - async function end() { - if (!alreadyEnded) { - alreadyEnded = true; - messenger.unsubscribe(eventType, eventListener); - - await beforeResolving(); - - if (interestingEventPayloads.length === expectedNumberOfEvents) { - resolve(interestingEventPayloads); - } else { - // Using a string instead of an Error leads to better backtraces. - /* eslint-disable-next-line prefer-promise-reject-errors */ - reject( - `Expected to receive ${expectedNumberOfEvents} ${eventType} event(s), but received ${ - interestingEventPayloads.length - } after ${timeBeforeAssumingNoMoreEvents}ms.\n\nAll payloads:\n\n${inspect( - allEventPayloads, - { depth: null }, - )}`, - ); - } - } - } - - function stopTimer() { - if (timer) { - clearTimeout(timer); - } - } - - function resetTimer() { - stopTimer(); - timer = setTimeout(() => { - end(); - }, timeBeforeAssumingNoMoreEvents); - } - - messenger.subscribe(eventType, eventListener); - resetTimer(); - }, - ); - - if (operation) { - await operation(); - } - - return await promiseForEventPayloads; -} - -/** - * Returns whether two places in different state objects have different values. - * - * @param currentState - The current state object. - * @param prevState - The previous state object. - * @param propertyPath - A property path within both objects. - * @returns True or false, depending on the result. - */ -function isStateChangeInteresting( - currentState: Record, - prevState: Record, - propertyPath: PropertyKey[], -): boolean { - return !isDeepStrictEqual( - get(currentState, propertyPath), - get(prevState, propertyPath), - ); -} diff --git a/app/scripts/controllers/network/network-controller.ts b/app/scripts/controllers/network/network-controller.ts deleted file mode 100644 index a4417a8e2..000000000 --- a/app/scripts/controllers/network/network-controller.ts +++ /dev/null @@ -1,1196 +0,0 @@ -import { strict as assert } from 'assert'; -import EventEmitter from 'events'; -import { ObservableStore } from '@metamask/obs-store'; -import log from 'loglevel'; -import { - createSwappableProxy, - createEventEmitterProxy, - SwappableProxy, -} from '@metamask/swappable-obj-proxy'; -import EthQuery from 'eth-query'; -import { RestrictedControllerMessenger } from '@metamask/base-controller'; -import { v4 as uuid } from 'uuid'; -import { Hex, isPlainObject, isStrictHexString } from '@metamask/utils'; -import { errorCodes } from 'eth-rpc-errors'; -import { SafeEventEmitterProvider } from '@metamask/eth-json-rpc-provider'; -import { PollingBlockTracker } from 'eth-block-tracker'; -import { hexToDecimal } from '../../../../shared/modules/conversion.utils'; -import { - INFURA_PROVIDER_TYPES, - INFURA_BLOCKED_KEY, - TEST_NETWORK_TICKER_MAP, - CHAIN_IDS, - NETWORK_TYPES, - BUILT_IN_INFURA_NETWORKS, - BuiltInInfuraNetwork, - NetworkStatus, -} from '../../../../shared/constants/network'; -import { - isPrefixedFormattedHexString, - isSafeChainId, -} from '../../../../shared/modules/network.utils'; -import { - MetaMetricsEventCategory, - MetaMetricsEventPayload, -} from '../../../../shared/constants/metametrics'; -import { isErrorWithMessage } from '../../../../shared/modules/error'; -import { - createNetworkClient, - NetworkClientType, -} from './create-network-client'; - -/** - * The name of NetworkController. - */ -const name = 'NetworkController'; - -/** - * A block header object that `eth_getBlockByNumber` returns. Note that this - * type does not specify all of the properties present within the block header; - * within NetworkController, we are only interested in `baseFeePerGas`. - */ -type Block = { - baseFeePerGas?: unknown; -}; - -/** - * Encodes a few pieces of information: - * - * - Whether or not a provider is configured for an Infura network or a - * non-Infura network. - * - If an Infura network, then which network. - * - If a non-Infura network, then whether the network exists locally or - * remotely. - * - * Primarily used to build the network client and check the availability of a - * network. - */ -export type ProviderType = BuiltInInfuraNetwork | typeof NETWORK_TYPES.RPC; - -/** - * The network ID of a network. - */ -type NetworkId = `${number}`; - -/** - * The ID of a network configuration. - */ -type NetworkConfigurationId = string; - -/** - * The chain ID of a network. - */ -type ChainId = Hex; - -/** - * `networkWillChange` is published when the current network is about to be - * switched, but the new provider has not been created and no state changes have - * occurred yet. - */ -export type NetworkControllerNetworkWillChangeEvent = { - type: 'NetworkController:networkWillChange'; - payload: []; -}; - -/** - * `networkDidChange` is published after a provider has been created for a newly - * switched network (but before the network has been confirmed to be available). - */ -export type NetworkControllerNetworkDidChangeEvent = { - type: 'NetworkController:networkDidChange'; - payload: []; -}; - -/** - * `infuraIsBlocked` is published after the network is switched to an Infura - * network, but when Infura returns an error blocking the user based on their - * location. - */ -export type NetworkControllerInfuraIsBlockedEvent = { - type: 'NetworkController:infuraIsBlocked'; - payload: []; -}; - -/** - * `infuraIsBlocked` is published either after the network is switched to an - * Infura network and Infura does not return an error blocking the user based on - * their location, or the network is switched to a non-Infura network. - */ -export type NetworkControllerInfuraIsUnblockedEvent = { - type: 'NetworkController:infuraIsUnblocked'; - payload: []; -}; - -/** - * The set of events that the NetworkController messenger can publish. - */ -export type NetworkControllerEvent = - | NetworkControllerNetworkDidChangeEvent - | NetworkControllerNetworkWillChangeEvent - | NetworkControllerInfuraIsBlockedEvent - | NetworkControllerInfuraIsUnblockedEvent; - -export type NetworkControllerGetProviderConfigAction = { - type: `NetworkController:getProviderConfig`; - handler: () => ProviderConfiguration; -}; - -export type NetworkControllerGetEthQueryAction = { - type: `NetworkController:getEthQuery`; - handler: () => EthQuery | undefined; -}; - -export type NetworkControllerAction = - | NetworkControllerGetProviderConfigAction - | NetworkControllerGetEthQueryAction; - -/** - * The messenger that the NetworkController uses to publish events. - */ -export type NetworkControllerMessenger = RestrictedControllerMessenger< - typeof name, - NetworkControllerAction, - NetworkControllerEvent, - string, - string ->; - -/** - * Information used to set up the middleware stack for a particular kind of - * network. Currently has overlap with `NetworkConfiguration`, although the - * two will be merged down the road. - */ -export type ProviderConfiguration = { - /** - * Either a type of Infura network, "localhost" for a locally operated - * network, or "rpc" for everything else. - */ - type: ProviderType; - /** - * The chain ID as per EIP-155. - */ - chainId: ChainId; - /** - * The URL of the RPC endpoint. Only used when `type` is "localhost" or "rpc". - */ - rpcUrl?: string; - /** - * The shortname of the currency used by the network. - */ - ticker?: string; - /** - * The user-customizable name of the network. - */ - nickname?: string; - /** - * User-customizable details for the network. - */ - rpcPrefs?: { - blockExplorerUrl?: string; - }; - /** - * The ID of the network configuration used to build this provider config. - */ - id?: NetworkConfigurationId; -}; - -/** - * The contents of the `networkId` store. - */ -type NetworkIdState = NetworkId | null; - -/** - * Information about the network not held by any other part of state. Currently - * only used to capture whether a network supports EIP-1559. - */ -type NetworkDetails = { - /** - * EIPs supported by the network. - */ - EIPS: { - [eipNumber: number]: boolean | undefined; - }; - [otherProperty: string]: unknown; -}; - -/** - * A "network configuration" represents connection data directly provided by - * users via the wallet UI for a custom network (we already have this - * information for networks that come pre-shipped with the wallet). Ultimately - * used to set up the middleware stack so that the wallet can make requests to - * the network. Currently has overlap with `ProviderConfiguration`, although the - * two will be merged down the road. - */ -type NetworkConfiguration = { - /** - * The unique ID of the network configuration. Useful for switching to and - * removing specific networks. - */ - id: NetworkConfigurationId; - /** - * The URL of the RPC endpoint. Only used when `type` is "localhost" or "rpc". - */ - rpcUrl: string; - /** - * The chain ID as per EIP-155. - */ - chainId: ChainId; - /** - * The shortname of the currency used for this network. - */ - ticker: string; - /** - * The user-customizable name of the network. - */ - nickname?: string; - /** - * User-customizable details for the network. - */ - rpcPrefs?: { - blockExplorerUrl: string; - }; -}; - -/** - * A set of network configurations, keyed by ID. - */ -type NetworkConfigurations = Record< - NetworkConfigurationId, - NetworkConfiguration ->; - -/** - * The state that NetworkController holds after combining its individual stores. - */ -export type NetworkControllerState = { - providerConfig: ProviderConfiguration; - networkId: NetworkIdState; - networkStatus: NetworkStatus; - networkDetails: NetworkDetails; - networkConfigurations: NetworkConfigurations; -}; - -/** - * The options that NetworkController takes. - */ -export type NetworkControllerOptions = { - messenger: NetworkControllerMessenger; - state?: { - providerConfig?: ProviderConfiguration; - networkDetails?: NetworkDetails; - networkConfigurations?: NetworkConfigurations; - }; - infuraProjectId: string; - trackMetaMetricsEvent: (payload: MetaMetricsEventPayload) => void; -}; - -/** - * Type guard for determining whether the given value is an error object with a - * `code` property, such as an instance of Error. - * - * TODO: Move this to @metamask/utils - * - * @param error - The object to check. - * @returns True if `error` has a `code`, false otherwise. - */ -function isErrorWithCode(error: unknown): error is { code: string | number } { - return typeof error === 'object' && error !== null && 'code' in error; -} - -/** - * Convert the given value into a valid network ID. The ID is accepted - * as either a number, a decimal string, or a 0x-prefixed hex string. - * - * @param value - The network ID to convert, in an unknown format. - * @returns A valid network ID (as a decimal string) - * @throws If the given value cannot be safely parsed. - */ -function convertNetworkId(value: unknown): NetworkId { - if (typeof value === 'number' && !Number.isNaN(value)) { - return `${value}`; - } else if (isStrictHexString(value)) { - return hexToDecimal(value) as NetworkId; - } else if (typeof value === 'string' && /^\d+$/u.test(value)) { - return value as NetworkId; - } - throw new Error(`Cannot parse as a valid network ID: '${value}'`); -} - -/** - * Builds the default provider config used to initialize the network controller. - */ -function buildDefaultProviderConfigState(): ProviderConfiguration { - if (process.env.IN_TEST) { - return { - type: NETWORK_TYPES.RPC, - rpcUrl: 'http://localhost:8545', - chainId: '0x539', - nickname: 'Localhost 8545', - ticker: 'ETH', - }; - } else if ( - process.env.METAMASK_DEBUG || - process.env.METAMASK_ENVIRONMENT === 'test' - ) { - return { - type: NETWORK_TYPES.GOERLI, - chainId: CHAIN_IDS.GOERLI, - ticker: TEST_NETWORK_TICKER_MAP[NETWORK_TYPES.GOERLI], - }; - } - - return { - type: NETWORK_TYPES.MAINNET, - chainId: CHAIN_IDS.MAINNET, - ticker: 'ETH', - }; -} - -/** - * Builds the default network ID state used to initialize the network - * controller. - */ -function buildDefaultNetworkIdState(): NetworkIdState { - return null; -} - -/** - * Builds the default network status state used to initialize the network - * controller. - */ -function buildDefaultNetworkStatusState(): NetworkStatus { - return NetworkStatus.Unknown; -} - -/** - * Builds the default network details state used to initialize the - * network controller. - */ -function buildDefaultNetworkDetailsState(): NetworkDetails { - return { - EIPS: {}, - }; -} - -/** - * Builds the default network configurations state used to initialize the - * network controller. - */ -function buildDefaultNetworkConfigurationsState(): NetworkConfigurations { - return {}; -} - -/** - * Builds the default state for the network controller. - * - * @returns The default network controller state. - */ -function buildDefaultState() { - return { - providerConfig: buildDefaultProviderConfigState(), - networkId: buildDefaultNetworkIdState(), - networkStatus: buildDefaultNetworkStatusState(), - networkDetails: buildDefaultNetworkDetailsState(), - networkConfigurations: buildDefaultNetworkConfigurationsState(), - }; -} - -/** - * Returns whether the given argument is a type that our Infura middleware - * recognizes. We can't calculate this inline because the usual type of `type`, - * which we get from the provider config, is not a subset of the type of - * `INFURA_PROVIDER_TYPES`, but rather a superset, and therefore we cannot make - * a proper comparison without TypeScript complaining. However, if we downcast - * both variables, then we are able to achieve this. As a bonus, this function - * also types the given argument as a `BuiltInInfuraNetwork` assuming that the - * check succeeds. - * - * @param type - A type to compare. - * @returns True or false, depending on whether the given type is one that our - * Infura middleware recognizes. - */ -function isInfuraProviderType(type: string): type is BuiltInInfuraNetwork { - const infuraProviderTypes: readonly string[] = INFURA_PROVIDER_TYPES; - return infuraProviderTypes.includes(type); -} - -/** - * The network controller creates and manages the "provider" object which allows - * our code and external dapps to make requests to a network. The requests are - * filtered through a set of middleware (provided by - * [`eth-json-rpc-middleware`][1]) which not only performs the HTTP request to - * the appropriate RPC endpoint but also uses caching to limit duplicate - * requests to Infura and smoothens interactions with the blockchain in general. - * - * [1]: https://github.com/MetaMask/eth-json-rpc-middleware - */ -export class NetworkController extends EventEmitter { - /** - * The messenger that NetworkController uses to publish events. - */ - #messenger: NetworkControllerMessenger; - - /** - * Observable store containing the provider configuration for the previously - * configured network. - */ - #previousProviderConfig: ProviderConfiguration; - - /** - * Observable store containing a combination of data from all of the - * individual stores. - */ - store: ObservableStore; - - #provider: SafeEventEmitterProvider | null; - - #blockTracker: PollingBlockTracker | null; - - #providerProxy: SwappableProxy | null; - - #blockTrackerProxy: SwappableProxy | null; - - #ethQuery: EthQuery | undefined; - - #infuraProjectId: NetworkControllerOptions['infuraProjectId']; - - #trackMetaMetricsEvent: NetworkControllerOptions['trackMetaMetricsEvent']; - - /** - * Constructs a network controller. - * - * @param options - Options for this constructor. - * @param options.messenger - The NetworkController messenger. - * @param options.state - Initial controller state. - * @param options.infuraProjectId - The Infura project ID. - * @param options.trackMetaMetricsEvent - A method to forward events to the - * {@link MetaMetricsController}. - */ - constructor({ - messenger, - state = {}, - infuraProjectId, - trackMetaMetricsEvent, - }: NetworkControllerOptions) { - super(); - - this.#messenger = messenger; - - this.store = new ObservableStore({ - ...buildDefaultState(), - ...state, - }); - this.#previousProviderConfig = this.store.getState().providerConfig; - - // provider and block tracker - this.#provider = null; - this.#blockTracker = null; - - // provider and block tracker proxies - because the network changes - this.#providerProxy = null; - this.#blockTrackerProxy = null; - - if (!infuraProjectId || typeof infuraProjectId !== 'string') { - throw new Error('Invalid Infura project ID'); - } - this.#infuraProjectId = infuraProjectId; - this.#trackMetaMetricsEvent = trackMetaMetricsEvent; - - this.#messenger.registerActionHandler(`${name}:getProviderConfig`, () => { - return this.store.getState().providerConfig; - }); - this.#messenger.registerActionHandler(`${name}:getEthQuery`, () => { - return this.#ethQuery; - }); - } - - /** - * Deactivates the controller, stopping any ongoing polling. - * - * In-progress requests will not be aborted. - */ - async destroy(): Promise { - await this.#blockTracker?.destroy(); - } - - /** - * Creates the provider and block tracker for the configured network, - * using the provider to gather details about the network. - */ - async initializeProvider(): Promise { - const { type, rpcUrl, chainId } = this.store.getState().providerConfig; - this.#configureProvider({ type, rpcUrl, chainId }); - await this.lookupNetwork(); - } - - /** - * Returns the proxies wrapping the currently set provider and block tracker. - */ - getProviderAndBlockTracker(): { - provider: SwappableProxy | null; - blockTracker: SwappableProxy | null; - } { - const provider = this.#providerProxy; - const blockTracker = this.#blockTrackerProxy; - return { provider, blockTracker }; - } - - /** - * Determines whether the network supports EIP-1559 by checking whether the - * latest block has a `baseFeePerGas` property, then updates state - * appropriately. - * - * @returns A promise that resolves to true if the network supports EIP-1559 - * and false otherwise. - */ - async getEIP1559Compatibility(): Promise { - const { EIPS } = this.store.getState().networkDetails; - // NOTE: This isn't necessary anymore because the block cache middleware - // already prevents duplicate requests from taking place - if (EIPS[1559] !== undefined) { - return EIPS[1559]; - } - - const { provider } = this.getProviderAndBlockTracker(); - if (!provider) { - // Really we should throw an error if a provider hasn't been initialized - // yet, but that might have undesirable repercussions, so return false for - // now - return false; - } - - const supportsEIP1559 = await this.#determineEIP1559Compatibility(provider); - const { networkDetails } = this.store.getState(); - this.store.updateState({ - networkDetails: { - ...networkDetails, - EIPS: { - ...networkDetails.EIPS, - 1559: supportsEIP1559, - }, - }, - }); - return supportsEIP1559; - } - - /** - * Performs side effects after switching to a network. If the network is - * available, updates the network state with the network ID of the network and - * stores whether the network supports EIP-1559; otherwise clears said - * information about the network that may have been previously stored. - * - * @fires infuraIsBlocked if the network is Infura-supported and is blocking - * requests. - * @fires infuraIsUnblocked if the network is Infura-supported and is not - * blocking requests, or if the network is not Infura-supported. - */ - async lookupNetwork(): Promise { - const { type } = this.store.getState().providerConfig; - const { provider } = this.getProviderAndBlockTracker(); - let networkChanged = false; - let networkId: NetworkIdState = null; - let supportsEIP1559 = false; - let networkStatus: NetworkStatus; - - if (provider === null) { - log.warn( - 'NetworkController - lookupNetwork aborted due to missing provider', - ); - return; - } - - const isInfura = isInfuraProviderType(type); - - const listener = () => { - networkChanged = true; - this.#messenger.unsubscribe( - 'NetworkController:networkDidChange', - listener, - ); - }; - this.#messenger.subscribe('NetworkController:networkDidChange', listener); - - try { - const results = await Promise.all([ - this.#getNetworkId(provider), - this.#determineEIP1559Compatibility(provider), - ]); - const possibleNetworkId = results[0]; - networkId = convertNetworkId(possibleNetworkId); - supportsEIP1559 = results[1]; - networkStatus = NetworkStatus.Available; - } catch (error) { - if (isErrorWithCode(error)) { - let responseBody; - if (isInfura && isErrorWithMessage(error)) { - try { - responseBody = JSON.parse(error.message); - } catch { - // error.message must not be JSON - } - } - - if ( - isPlainObject(responseBody) && - responseBody.error === INFURA_BLOCKED_KEY - ) { - networkStatus = NetworkStatus.Blocked; - } else if (error.code === errorCodes.rpc.internal) { - networkStatus = NetworkStatus.Unknown; - } else { - networkStatus = NetworkStatus.Unavailable; - } - } else { - log.warn( - 'NetworkController - could not determine network status', - error, - ); - networkStatus = NetworkStatus.Unknown; - } - } - - if (networkChanged) { - // If the network has changed, then `lookupNetwork` either has been or is - // in the process of being called, so we don't need to go further. - return; - } - this.#messenger.unsubscribe('NetworkController:networkDidChange', listener); - - this.store.updateState({ - networkStatus, - }); - - if (networkStatus === NetworkStatus.Available) { - const { networkDetails } = this.store.getState(); - this.store.updateState({ - networkId, - networkDetails: { - ...networkDetails, - EIPS: { - ...networkDetails.EIPS, - 1559: supportsEIP1559, - }, - }, - }); - } else { - this.#resetNetworkId(); - this.#resetNetworkDetails(); - } - - if (isInfura) { - if (networkStatus === NetworkStatus.Available) { - this.#messenger.publish('NetworkController:infuraIsUnblocked'); - } else if (networkStatus === NetworkStatus.Blocked) { - this.#messenger.publish('NetworkController:infuraIsBlocked'); - } - } else { - // Always publish infuraIsUnblocked regardless of network status to - // prevent consumers from being stuck in a blocked state if they were - // previously connected to an Infura network that was blocked - this.#messenger.publish('NetworkController:infuraIsUnblocked'); - } - } - - /** - * Switches to the network specified by a network configuration. - * - * @param networkConfigurationId - The unique identifier that refers to a - * previously added network configuration. - * @returns The URL of the RPC endpoint representing the newly switched - * network. - */ - async setActiveNetwork(networkConfigurationId: NetworkConfigurationId) { - const targetNetwork = - this.store.getState().networkConfigurations[networkConfigurationId]; - - if (!targetNetwork) { - throw new Error( - `networkConfigurationId ${networkConfigurationId} does not match a configured networkConfiguration`, - ); - } - - await this.#setProviderConfig({ - type: NETWORK_TYPES.RPC, - ...targetNetwork, - }); - - return targetNetwork.rpcUrl; - } - - /** - * Switches to an Infura-supported network. - * - * @param type - The shortname of the network. - * @throws if the `type` is "rpc" or if it is not a known Infura-supported - * network. - */ - async setProviderType(type: string) { - assert.notStrictEqual( - type, - NETWORK_TYPES.RPC, - `NetworkController - cannot call "setProviderType" with type "${NETWORK_TYPES.RPC}". Use "setActiveNetwork"`, - ); - assert.ok( - isInfuraProviderType(type), - `Unknown Infura provider type "${type}".`, - ); - const network = BUILT_IN_INFURA_NETWORKS[type]; - await this.#setProviderConfig({ - type, - rpcUrl: undefined, - chainId: network.chainId, - ticker: 'ticker' in network ? network.ticker : 'ETH', - nickname: undefined, - rpcPrefs: { blockExplorerUrl: network.blockExplorerUrl }, - id: undefined, - }); - } - - /** - * Re-initializes the provider and block tracker for the current network. - */ - async resetConnection() { - await this.#setProviderConfig(this.store.getState().providerConfig); - } - - /** - * Switches to the previous network, assuming that the current network is - * different than the initial network (if it is, then this is equivalent to - * calling `resetConnection`). - */ - async rollbackToPreviousProvider() { - const config = this.#previousProviderConfig; - this.store.updateState({ - providerConfig: config, - }); - await this.#switchNetwork(config); - } - - /** - * Fetches the latest block for the network. - * - * @param provider - A provider, which is guaranteed to be available. - * @returns A promise that either resolves to the block header or null if - * there is no latest block, or rejects with an error. - */ - #getLatestBlock(provider: SafeEventEmitterProvider): Promise { - return new Promise((resolve, reject) => { - const ethQuery = new EthQuery(provider); - ethQuery.sendAsync<['latest', false], Block | null>( - { method: 'eth_getBlockByNumber', params: ['latest', false] }, - (...args) => { - if (args[0] === null) { - resolve(args[1]); - } else { - reject(args[0]); - } - }, - ); - }); - } - - /** - * Fetches the network ID for the network. - * - * @param provider - A provider, which is guaranteed to be available. - * @returns A promise that either resolves to the network ID, or rejects with - * an error. - */ - async #getNetworkId(provider: SafeEventEmitterProvider): Promise { - const ethQuery = new EthQuery(provider); - return await new Promise((resolve, reject) => { - ethQuery.sendAsync( - { method: 'net_version' }, - (...args) => { - if (args[0] === null) { - resolve(args[1]); - } else { - reject(args[0]); - } - }, - ); - }); - } - - /** - * Clears the stored network ID. - */ - #resetNetworkId(): void { - this.store.updateState({ - networkId: buildDefaultNetworkIdState(), - }); - } - - /** - * Resets network status to the default ("unknown"). - */ - #resetNetworkStatus(): void { - this.store.updateState({ - networkStatus: buildDefaultNetworkStatusState(), - }); - } - - /** - * Clears details previously stored for the network. - */ - #resetNetworkDetails(): void { - this.store.updateState({ - networkDetails: buildDefaultNetworkDetailsState(), - }); - } - - /** - * Stores the given provider configuration representing a network in state, - * then uses it to create a new provider for that network. - * - * @param providerConfig - The provider configuration. - */ - async #setProviderConfig(providerConfig: ProviderConfiguration) { - this.#previousProviderConfig = this.store.getState().providerConfig; - this.store.updateState({ providerConfig }); - await this.#switchNetwork(providerConfig); - } - - /** - * Retrieves the latest block from the currently selected network; if the - * block has a `baseFeePerGas` property, then we know that the network - * supports EIP-1559; otherwise it doesn't. - * - * @param provider - A provider, which is guaranteed to be available. - * @returns A promise that resolves to true if the network supports EIP-1559 - * and false otherwise. - */ - async #determineEIP1559Compatibility( - provider: SafeEventEmitterProvider, - ): Promise { - const latestBlock = await this.#getLatestBlock(provider); - return latestBlock?.baseFeePerGas !== undefined; - } - - /** - * Executes a series of steps to change the current network: - * - * 1. Notifies subscribers that the network is about to change. - * 2. Clears state associated with the current network. - * 3. Creates a new network client along with a provider for the desired - * network. - * 4. Notifies subscribes that the network has changed. - * - * @param providerConfig - The provider configuration object that specifies - * the new network. - */ - async #switchNetwork(providerConfig: ProviderConfiguration) { - const { type, rpcUrl, chainId } = providerConfig; - this.#messenger.publish('NetworkController:networkWillChange'); - this.#resetNetworkId(); - this.#resetNetworkStatus(); - this.#resetNetworkDetails(); - this.#configureProvider({ type, rpcUrl, chainId }); - this.#messenger.publish('NetworkController:networkDidChange'); - await this.lookupNetwork(); - } - - /** - * Creates a network client (a stack of middleware along with a provider and - * block tracker) to talk to a network. - * - * @param args - The arguments. - * @param args.type - The provider type. - * @param args.rpcUrl - The URL of the RPC endpoint that represents the - * network. Only used for non-Infura networks. - * @param args.chainId - The chain ID of the network (as per EIP-155). Only - * used for non-Infura-supported networks (as we already know the chain ID of - * any Infura-supported network). - * @throws if the `type` if not a known Infura-supported network. - */ - #configureProvider({ - type, - rpcUrl, - chainId, - }: { - type: ProviderType; - rpcUrl: string | undefined; - chainId: Hex | undefined; - }): void { - const isInfura = isInfuraProviderType(type); - if (isInfura) { - this.#configureInfuraProvider({ - type, - infuraProjectId: this.#infuraProjectId, - }); - } else if (type === NETWORK_TYPES.RPC) { - if (chainId === undefined) { - throw new Error('chainId must be provided for custom RPC endpoints'); - } - if (rpcUrl === undefined) { - throw new Error('rpcUrl must be provided for custom RPC endpoints'); - } - this.#configureStandardProvider(rpcUrl, chainId); - } else { - throw new Error(`Unrecognized network type: '${type}'`); - } - } - - /** - * Creates a new instance of EthQuery that wraps the current provider and - * saves it for future usage. - */ - #registerProvider() { - const { provider } = this.getProviderAndBlockTracker(); - - if (provider) { - this.#ethQuery = new EthQuery(provider); - } - } - - /** - * Creates a network client (a stack of middleware along with a provider and - * block tracker) to talk to an Infura-supported network. - * - * @param args - The arguments. - * @param args.type - The shortname of the Infura network (see - * {@link NETWORK_TYPES}). - * @param args.infuraProjectId - An Infura API key. ("Project ID" is a - * now-obsolete term we've retained for backward compatibility.) - */ - #configureInfuraProvider({ - type, - infuraProjectId, - }: { - type: BuiltInInfuraNetwork; - infuraProjectId: NetworkControllerOptions['infuraProjectId']; - }): void { - log.info('NetworkController - #configureInfuraProvider', type); - const { provider, blockTracker } = createNetworkClient({ - network: type, - infuraProjectId, - type: NetworkClientType.Infura, - }); - this.#updateProvider(provider, blockTracker); - } - - /** - * Creates a network client (a stack of middleware along with a provider and - * block tracker) to talk to a non-Infura-supported network. - * - * @param rpcUrl - The URL of the RPC endpoint that represents the network. - * @param chainId - The chain ID of the network (as per EIP-155). - */ - #configureStandardProvider(rpcUrl: string, chainId: ChainId): void { - log.info('NetworkController - #configureStandardProvider', rpcUrl); - const { provider, blockTracker } = createNetworkClient({ - chainId, - rpcUrl, - type: NetworkClientType.Custom, - }); - this.#updateProvider(provider, blockTracker); - } - - /** - * Given a provider and a block tracker, updates any proxies pointing to - * these objects that have been previously set, or initializes any proxies - * that have not been previously set, then creates an instance of EthQuery - * that wraps the provider. - * - * @param provider - The provider. - * @param blockTracker - The block tracker. - */ - #updateProvider( - provider: SafeEventEmitterProvider, - blockTracker: PollingBlockTracker, - ) { - this.#setProviderAndBlockTracker({ - provider, - blockTracker, - }); - this.#registerProvider(); - } - - /** - * Given a provider and a block tracker, updates any proxies pointing to - * these objects that have been previously set, or initializes any proxies - * that have not been previously set. - * - * @param args - The arguments. - * @param args.provider - The provider. - * @param args.blockTracker - The block tracker. - */ - #setProviderAndBlockTracker({ - provider, - blockTracker, - }: { - provider: SafeEventEmitterProvider; - blockTracker: PollingBlockTracker; - }): void { - // update or initialize proxies - if (this.#providerProxy) { - this.#providerProxy.setTarget(provider); - } else { - this.#providerProxy = createSwappableProxy(provider); - } - if (this.#blockTrackerProxy) { - this.#blockTrackerProxy.setTarget(blockTracker); - } else { - this.#blockTrackerProxy = createEventEmitterProxy(blockTracker, { - eventFilter: 'skipInternal', - }); - } - // set new provider and blockTracker - this.#provider = provider; - this.#blockTracker = blockTracker; - } - - /** - * Network Configuration management functions - */ - - /** - * Updates an existing network configuration matching the same RPC URL as the - * given network configuration; otherwise adds the network configuration. - * Following the upsert, the `trackMetaMetricsEvent` callback specified - * via the NetworkController constructor will be called to (presumably) create - * a MetaMetrics event. - * - * @param networkConfiguration - The network configuration to upsert. - * @param networkConfiguration.chainId - The chain ID of the network as per - * EIP-155. - * @param networkConfiguration.ticker - The shortname of the currency used by - * the network. - * @param networkConfiguration.nickname - The user-customizable name of the - * network. - * @param networkConfiguration.rpcPrefs - User-customizable details for the - * network. - * @param networkConfiguration.rpcUrl - The URL of the RPC endpoint. - * @param additionalArgs - Additional arguments. - * @param additionalArgs.setActive - Switches to the network specified by - * the given network configuration following the upsert. - * @param additionalArgs.referrer - The site from which the call originated, - * or 'metamask' for internal calls; used for event metrics. - * @param additionalArgs.source - Where the metric event originated (i.e. from - * a dapp or from the network form); used for event metrics. - * @throws if the `chainID` does not match EIP-155 or is too large. - * @throws if `rpcUrl` is not a valid URL. - * @returns The ID for the added or updated network configuration. - */ - async upsertNetworkConfiguration( - { - rpcUrl, - chainId, - ticker, - nickname, - rpcPrefs, - }: Omit, - { - setActive = false, - referrer, - source, - }: { - setActive?: boolean; - referrer: string; - source: string; - }, - ): Promise { - assert.ok( - isPrefixedFormattedHexString(chainId), - `Invalid chain ID "${chainId}": invalid hex string.`, - ); - assert.ok( - isSafeChainId(parseInt(chainId, 16)), - `Invalid chain ID "${chainId}": numerical value greater than max safe value.`, - ); - - if (!rpcUrl) { - throw new Error( - 'An rpcUrl is required to add or update network configuration', - ); - } - - if (!referrer || !source) { - throw new Error( - 'referrer and source are required arguments for adding or updating a network configuration', - ); - } - - try { - // eslint-disable-next-line no-new - new URL(rpcUrl); - } catch (e) { - if (isErrorWithMessage(e) && e.message.includes('Invalid URL')) { - throw new Error('rpcUrl must be a valid URL'); - } - } - - if (!ticker) { - throw new Error( - 'A ticker is required to add or update networkConfiguration', - ); - } - - const { networkConfigurations } = this.store.getState(); - const newNetworkConfiguration = { - rpcUrl, - chainId, - ticker, - nickname, - rpcPrefs, - }; - - const oldNetworkConfigurationId = Object.values(networkConfigurations).find( - (networkConfiguration) => - networkConfiguration.rpcUrl?.toLowerCase() === rpcUrl?.toLowerCase(), - )?.id; - - const newNetworkConfigurationId = oldNetworkConfigurationId || uuid(); - this.store.updateState({ - networkConfigurations: { - ...networkConfigurations, - [newNetworkConfigurationId]: { - ...newNetworkConfiguration, - id: newNetworkConfigurationId, - }, - }, - }); - - if (!oldNetworkConfigurationId) { - this.#trackMetaMetricsEvent({ - event: 'Custom Network Added', - category: MetaMetricsEventCategory.Network, - referrer: { - url: referrer, - }, - properties: { - chain_id: chainId, - symbol: ticker, - source, - }, - }); - } - - if (setActive) { - await this.setActiveNetwork(newNetworkConfigurationId); - } - - return newNetworkConfigurationId; - } - - /** - * Removes a network configuration from state. - * - * @param networkConfigurationId - The unique id for the network configuration - * to remove. - */ - removeNetworkConfiguration(networkConfigurationId: NetworkConfigurationId) { - if (!this.store.getState().networkConfigurations[networkConfigurationId]) { - throw new Error( - `networkConfigurationId ${networkConfigurationId} does not match a configured networkConfiguration`, - ); - } - const networkConfigurations = { - ...this.store.getState().networkConfigurations, - }; - delete networkConfigurations[networkConfigurationId]; - this.store.updateState({ - networkConfigurations, - }); - } -} diff --git a/app/scripts/controllers/network/provider-api-tests/block-hash-in-response.ts b/app/scripts/controllers/network/provider-api-tests/block-hash-in-response.ts deleted file mode 100644 index 4ee0f633d..000000000 --- a/app/scripts/controllers/network/provider-api-tests/block-hash-in-response.ts +++ /dev/null @@ -1,284 +0,0 @@ -/* eslint-disable jest/require-top-level-describe, jest/no-export */ - -import { - ProviderType, - withMockedCommunications, - withNetworkClient, -} from './helpers'; - -type TestsForRpcMethodThatCheckForBlockHashInResponseOptions = { - providerType: ProviderType; - numberOfParameters: number; -}; - -/** - * 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. - * @param additionalArgs - Additional arguments. - * @param additionalArgs.numberOfParameters - The number of parameters supported - * by the method under test. - * @param additionalArgs.providerType - The type of provider being tested; - * either `infura` or `custom` (default: "infura"). - */ -export function testsForRpcMethodsThatCheckForBlockHashInResponse( - method: string, - { - numberOfParameters, - providerType, - }: TestsForRpcMethodThatCheckForBlockHashInResponseOptions, -) { - if (providerType !== 'infura' && providerType !== 'custom') { - throw new Error( - `providerType must be either "infura" or "custom", was "${providerType}" instead`, - ); - } - - it('does not hit the RPC endpoint more than once for identical requests and it has a valid blockHash', async () => { - const requests = [{ method }, { method }]; - const mockResult = { blockHash: '0x1' }; - - await withMockedCommunications({ providerType }, 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.mockRpcCall({ - request: requests[0], - response: { result: mockResult }, - }); - - const results = await withNetworkClient( - { providerType }, - ({ makeRpcCallsInSeries }) => makeRpcCallsInSeries(requests), - ); - - expect(results).toStrictEqual([mockResult, mockResult]); - }); - }); - - it('hits the RPC endpoint 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 withMockedCommunications({ providerType }, 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.mockRpcCall({ - request: requests[0], - response: { result: mockResults[0] }, - }); - comms.mockNextBlockTrackerRequest({ blockNumber: '0x2' }); - comms.mockRpcCall({ - request: requests[1], - response: { result: mockResults[1] }, - }); - - const results = await withNetworkClient( - { providerType }, - 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('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 withMockedCommunications({ providerType }, 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.mockRpcCall({ - request: requests[0], - response: { result: mockResults[0] }, - }); - comms.mockRpcCall({ - request: requests[1], - response: { result: mockResults[1] }, - }); - - const results = await withNetworkClient( - { providerType }, - ({ 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 withMockedCommunications({ providerType }, 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.mockRpcCall({ - request: requests[0], - response: { result: mockResults[0] }, - }); - comms.mockRpcCall({ - request: requests[1], - response: { result: mockResults[1] }, - }); - - const results = await withNetworkClient( - { providerType }, - ({ 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 withMockedCommunications({ providerType }, 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.mockRpcCall({ - request: requests[0], - response: { result: mockResults[0] }, - }); - comms.mockRpcCall({ - request: requests[1], - response: { result: mockResults[1] }, - }); - - const results = await withNetworkClient( - { providerType }, - ({ makeRpcCallsInSeries }) => makeRpcCallsInSeries(requests), - ); - - expect(results).toStrictEqual(mockResults); - }); - }); - - for (const emptyValue of [null, undefined, '\u003cnil\u003e']) { - it(`does not retry an empty response of "${emptyValue}"`, async () => { - const request = { method }; - const mockResult = emptyValue; - - await withMockedCommunications({ providerType }, 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.mockRpcCall({ - request, - response: { result: mockResult }, - }); - - const result = await withNetworkClient( - { providerType }, - ({ makeRpcCall }) => makeRpcCall(request), - ); - - expect(result).toStrictEqual(mockResult); - }); - }); - - it(`does not reuse the result of a previous request if it was "${emptyValue}"`, async () => { - const requests = [{ method }, { method }]; - const mockResults = [emptyValue, { blockHash: '0x100' }]; - - await withMockedCommunications({ providerType }, 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.mockRpcCall({ - request: requests[0], - response: { result: mockResults[0] }, - }); - comms.mockRpcCall({ - request: requests[1], - response: { result: mockResults[1] }, - }); - - const results = await withNetworkClient( - { providerType }, - ({ makeRpcCallsInSeries }) => makeRpcCallsInSeries(requests), - ); - - expect(results).toStrictEqual(mockResults); - }); - }); - } - - for (const paramIndex of [...Array(numberOfParameters).keys()]) { - it(`does not reuse the result of a previous request with a valid blockHash if parameter at index "${paramIndex}" differs`, async () => { - const firstMockParams = [ - ...new Array(numberOfParameters).fill('some value'), - ]; - const secondMockParams = firstMockParams.slice(); - secondMockParams[paramIndex] = 'another value'; - const requests = [ - { - method, - params: firstMockParams, - }, - { method, params: secondMockParams }, - ]; - const mockResults = [{ blockHash: '0x100' }, { blockHash: '0x200' }]; - - await withMockedCommunications({ providerType }, 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.mockRpcCall({ - request: requests[0], - response: { result: mockResults[0] }, - }); - comms.mockRpcCall({ - request: requests[1], - response: { result: mockResults[1] }, - }); - - const results = await withNetworkClient( - { providerType }, - ({ makeRpcCallsInSeries }) => makeRpcCallsInSeries(requests), - ); - - expect(results).toStrictEqual([mockResults[0], mockResults[1]]); - }); - }); - } -} diff --git a/app/scripts/controllers/network/provider-api-tests/block-param.ts b/app/scripts/controllers/network/provider-api-tests/block-param.ts deleted file mode 100644 index 92fb65f04..000000000 --- a/app/scripts/controllers/network/provider-api-tests/block-param.ts +++ /dev/null @@ -1,2070 +0,0 @@ -/* eslint-disable jest/require-top-level-describe, jest/no-export */ - -import { - buildMockParams, - buildRequestWithReplacedBlockParam, - ProviderType, - waitForPromiseToBeFulfilledAfterRunningAllTimers, - withMockedCommunications, - withNetworkClient, -} from './helpers'; -import { - buildFetchFailedErrorMessage, - buildInfuraClientRetriesExhaustedErrorMessage, - buildJsonRpcEngineEmptyResponseErrorMessage, -} from './shared-tests'; - -type TestsForRpcMethodSupportingBlockParam = { - providerType: ProviderType; - blockParamIndex: number; - numberOfParameters: number; -}; - -/** - * 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. - * - * @param method - The name of the RPC method under test. - * @param additionalArgs - Additional arguments. - * @param additionalArgs.blockParamIndex - The index of the block parameter. - * @param additionalArgs.numberOfParameters - The number of parameters supported by the method under test. - * @param additionalArgs.providerType - The type of provider being tested. - * either `infura` or `custom` (default: "infura"). - */ -/* eslint-disable-next-line jest/no-export */ -export function testsForRpcMethodSupportingBlockParam( - method: string, - { - blockParamIndex, - numberOfParameters, - providerType, - }: TestsForRpcMethodSupportingBlockParam, -) { - describe.each([ - ['given no block tag', undefined], - ['given a block tag of "latest"', 'latest'], - ])('%s', (_desc, blockParam) => { - it('does not hit the RPC endpoint more than once for identical requests', async () => { - const requests = [ - { - method, - params: buildMockParams({ blockParamIndex, blockParam }), - }, - { method, params: buildMockParams({ blockParamIndex, blockParam }) }, - ]; - const mockResults = ['first result', 'second result']; - - await withMockedCommunications({ providerType }, 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.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - requests[0], - blockParamIndex, - '0x100', - ), - response: { result: mockResults[0] }, - }); - - const results = await withNetworkClient( - { providerType }, - ({ makeRpcCallsInSeries }) => makeRpcCallsInSeries(requests), - ); - - expect(results).toStrictEqual([mockResults[0], mockResults[0]]); - }); - }); - - for (const paramIndex of [...Array(numberOfParameters).keys()]) { - if (paramIndex === blockParamIndex) { - // testing changes in block param is covered under later tests - continue; - } - - it(`does not reuse the result of a previous request if parameter at index "${paramIndex}" differs`, async () => { - const firstMockParams = [ - ...new Array(numberOfParameters).fill('some value'), - ]; - firstMockParams[blockParamIndex] = blockParam; - const secondMockParams = firstMockParams.slice(); - secondMockParams[paramIndex] = 'another value'; - const requests = [ - { - method, - params: firstMockParams, - }, - { method, params: secondMockParams }, - ]; - const mockResults = ['first result', 'second result']; - - await withMockedCommunications({ providerType }, 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.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - requests[0], - blockParamIndex, - '0x100', - ), - response: { result: mockResults[0] }, - }); - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - requests[1], - blockParamIndex, - '0x100', - ), - response: { result: mockResults[1] }, - }); - - const results = await withNetworkClient( - { providerType }, - ({ makeRpcCallsInSeries }) => makeRpcCallsInSeries(requests), - ); - - expect(results).toStrictEqual([mockResults[0], mockResults[1]]); - }); - }); - } - - it('hits the RPC endpoint and does not reuse the result of a previous request if the latest block number was updated since', async () => { - const requests = [ - { method, params: buildMockParams({ blockParamIndex, blockParam }) }, - { method, params: buildMockParams({ blockParamIndex, blockParam }) }, - ]; - const mockResults = ['first result', 'second result']; - - await withMockedCommunications({ providerType }, 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.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - requests[0], - blockParamIndex, - '0x100', - ), - response: { result: mockResults[0] }, - }); - comms.mockNextBlockTrackerRequest({ blockNumber: '0x200' }); - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - requests[1], - blockParamIndex, - '0x200', - ), - response: { result: mockResults[1] }, - }); - - const results = await withNetworkClient( - { providerType }, - 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); - }); - }); - - for (const emptyValue of [null, undefined, '\u003cnil\u003e']) { - it(`does not retry an empty response of "${emptyValue}"`, async () => { - const request = { - method, - params: buildMockParams({ blockParamIndex, blockParam }), - }; - const mockResult = emptyValue; - - await withMockedCommunications({ providerType }, 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.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - request, - blockParamIndex, - '0x100', - ), - response: { result: mockResult }, - }); - - const result = await withNetworkClient( - { providerType }, - ({ makeRpcCall }) => makeRpcCall(request), - ); - - expect(result).toStrictEqual(mockResult); - }); - }); - - it(`does not reuse the result of a previous request if it was "${emptyValue}"`, async () => { - const requests = [ - { method, params: buildMockParams({ blockParamIndex, blockParam }) }, - { method, params: buildMockParams({ blockParamIndex, blockParam }) }, - ]; - const mockResults = [emptyValue, 'some result']; - - await withMockedCommunications({ providerType }, 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.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - requests[0], - blockParamIndex, - '0x100', - ), - response: { result: mockResults[0] }, - }); - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - requests[1], - blockParamIndex, - '0x100', - ), - response: { result: mockResults[1] }, - }); - - const results = await withNetworkClient( - { providerType }, - ({ makeRpcCallsInSeries }) => makeRpcCallsInSeries(requests), - ); - - expect(results).toStrictEqual(mockResults); - }); - }); - } - - it('queues requests while a previous identical call is still pending, then runs the queue when it finishes, reusing the result from the first request', async () => { - const requests = [ - { method, params: buildMockParams({ blockParamIndex, blockParam }) }, - { method, params: buildMockParams({ blockParamIndex, blockParam }) }, - { method, params: buildMockParams({ blockParamIndex, blockParam }) }, - ]; - const mockResults = ['first result', 'second result', 'third result']; - - await withMockedCommunications({ providerType }, 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, and we delay it. - comms.mockRpcCall({ - delay: 100, - request: buildRequestWithReplacedBlockParam( - requests[0], - blockParamIndex, - '0x100', - ), - response: { result: mockResults[0] }, - }); - // The previous two requests will happen again, in the same order. - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - requests[1], - blockParamIndex, - '0x100', - ), - response: { result: mockResults[1] }, - }); - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - requests[2], - blockParamIndex, - '0x100', - ), - response: { result: mockResults[2] }, - }); - - const results = await withNetworkClient( - { providerType }, - async (client) => { - const resultPromises = [ - client.makeRpcCall(requests[0]), - client.makeRpcCall(requests[1]), - client.makeRpcCall(requests[2]), - ]; - const firstResult = await resultPromises[0]; - // The inflight cache middleware uses setTimeout to run the - // handlers, so run them now - client.clock.runAll(); - const remainingResults = await Promise.all(resultPromises.slice(1)); - return [firstResult, ...remainingResults]; - }, - ); - - expect(results).toStrictEqual([ - mockResults[0], - mockResults[0], - mockResults[0], - ]); - }); - }); - - it('throws an error with a custom message if the request to the RPC endpoint returns a 405 response', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { method }; - - // 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.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - request, - blockParamIndex, - '0x100', - ), - response: { - httpStatus: 405, - }, - }); - const promiseForResult = withNetworkClient( - { providerType }, - async ({ makeRpcCall }) => makeRpcCall(request), - ); - - await expect(promiseForResult).rejects.toThrow( - 'The method does not exist / is not available', - ); - }); - }); - - // There is a difference in how we are testing the Infura middleware vs. the - // custom RPC middleware (or, more specifically, the fetch middleware) - // because of what both middleware treat as rate limiting errors. In this - // case, the fetch middleware treats a 418 response from the RPC endpoint as - // such an error, whereas to the Infura middleware, it is a 429 response. - if (providerType === 'infura') { - it('throws a generic, undescriptive error if the request to the RPC endpoint returns a 418 response', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { - id: 123, - method, - params: buildMockParams({ blockParam, blockParamIndex }), - }; - - // 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.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - request, - blockParamIndex, - '0x100', - ), - response: { - httpStatus: 418, - }, - }); - const promiseForResult = withNetworkClient( - { providerType }, - async ({ makeRpcCall }) => makeRpcCall(request), - ); - - await expect(promiseForResult).rejects.toThrow( - '{"id":123,"jsonrpc":"2.0"}', - ); - }); - }); - - it('throws an error with a custom message if the request to the RPC endpoint returns a 429 response', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { - method, - params: buildMockParams({ blockParam, blockParamIndex }), - }; - - // 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.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - request, - blockParamIndex, - '0x100', - ), - response: { - httpStatus: 429, - }, - }); - const promiseForResult = withNetworkClient( - { providerType }, - async ({ makeRpcCall }) => makeRpcCall(request), - ); - - await expect(promiseForResult).rejects.toThrow( - 'Request is being rate limited', - ); - }); - }); - } else { - it('throws an error with a custom message if the request to the RPC endpoint returns a 418 response', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { - method, - params: buildMockParams({ blockParam, blockParamIndex }), - }; - - // 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.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - request, - blockParamIndex, - '0x100', - ), - response: { - httpStatus: 418, - }, - }); - const promiseForResult = withNetworkClient( - { providerType }, - async ({ makeRpcCall }) => makeRpcCall(request), - ); - - await expect(promiseForResult).rejects.toThrow( - 'Request is being rate limited.', - ); - }); - }); - - it('throws an undescriptive error if the request to the RPC endpoint returns a 429 response', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { - method, - params: buildMockParams({ blockParam, blockParamIndex }), - }; - - // 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.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - request, - blockParamIndex, - '0x100', - ), - response: { - httpStatus: 429, - }, - }); - const promiseForResult = withNetworkClient( - { providerType }, - async ({ makeRpcCall }) => makeRpcCall(request), - ); - - await expect(promiseForResult).rejects.toThrow( - "Non-200 status code: '429'", - ); - }); - }); - } - - it('throws an undescriptive error message if the request to the RPC endpoint returns a response that is not 405, 418, 429, 503, or 504', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { method }; - - // 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.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - request, - blockParamIndex, - '0x100', - ), - response: { - id: 12345, - jsonrpc: '2.0', - error: 'some error', - httpStatus: 420, - }, - }); - const promiseForResult = withNetworkClient( - { providerType }, - async ({ makeRpcCall }) => makeRpcCall(request), - ); - - const msg = - providerType === 'infura' - ? '{"id":12345,"jsonrpc":"2.0","error":"some error"}' - : "Non-200 status code: '420'"; - await expect(promiseForResult).rejects.toThrow(msg); - }); - }); - - [503, 504].forEach((httpStatus) => { - it(`retries the request to the RPC endpoint up to 5 times if it returns a ${httpStatus} response, returning the successful result if there is one on the 5th try`, async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { - method, - params: buildMockParams({ blockParam, blockParamIndex }), - }; - - // 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. - // - // Here we have the request fail for the first 4 tries, then succeed - // on the 5th try. - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - request, - blockParamIndex, - '0x100', - ), - response: { - error: 'some error', - httpStatus, - }, - times: 4, - }); - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - request, - blockParamIndex, - '0x100', - ), - response: { - result: 'the result', - httpStatus: 200, - }, - }); - const result = await withNetworkClient( - { providerType }, - async ({ makeRpcCall, clock }) => { - return await waitForPromiseToBeFulfilledAfterRunningAllTimers( - makeRpcCall(request), - clock, - ); - }, - ); - - expect(result).toStrictEqual('the result'); - }); - }); - - // Both the Infura middleware and custom RPC middleware detect a 503 or - // 504 response and retry the request to the RPC endpoint automatically - // but differ in what sort of response is returned when the number of - // retries is exhausted. - if (providerType === 'infura') { - it(`causes a request to fail with a custom error if the request to the RPC endpoint returns a ${httpStatus} response 5 times in a row`, async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { - method, - params: buildMockParams({ blockParam, blockParamIndex }), - }; - - // 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.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - request, - blockParamIndex, - '0x100', - ), - response: { - error: 'Some error', - httpStatus, - }, - times: 5, - }); - const promiseForResult = withNetworkClient( - { providerType }, - async ({ makeRpcCall, clock }) => { - return await waitForPromiseToBeFulfilledAfterRunningAllTimers( - makeRpcCall(request), - clock, - ); - }, - ); - await expect(promiseForResult).rejects.toThrow( - buildInfuraClientRetriesExhaustedErrorMessage('Gateway timeout'), - ); - }); - }); - } else { - it(`produces an empty response if the request to the RPC endpoint returns a ${httpStatus} response 5 times in a row`, async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { - method, - params: buildMockParams({ blockParam, blockParamIndex }), - }; - - // 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.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - request, - blockParamIndex, - '0x100', - ), - response: { - error: 'Some error', - httpStatus, - }, - times: 5, - }); - const promiseForResult = withNetworkClient( - { providerType }, - async ({ makeRpcCall, clock }) => { - return await waitForPromiseToBeFulfilledAfterRunningAllTimers( - makeRpcCall(request), - clock, - ); - }, - ); - await expect(promiseForResult).rejects.toThrow( - buildJsonRpcEngineEmptyResponseErrorMessage(method), - ); - }); - }); - } - }); - - it('retries the request to the RPC endpoint up to 5 times if an "ETIMEDOUT" error is thrown while making the request, returning the successful result if there is one on the 5th try', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { - method, - params: buildMockParams({ blockParam, blockParamIndex }), - }; - - // 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. - // - // Here we have the request fail for the first 4 tries, then - // succeed on the 5th try. - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - request, - blockParamIndex, - '0x100', - ), - error: 'ETIMEDOUT: Some message', - times: 4, - }); - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - request, - blockParamIndex, - '0x100', - ), - response: { - result: 'the result', - httpStatus: 200, - }, - }); - - const result = await withNetworkClient( - { providerType }, - async ({ makeRpcCall, clock }) => { - return await waitForPromiseToBeFulfilledAfterRunningAllTimers( - makeRpcCall(request), - clock, - ); - }, - ); - - expect(result).toStrictEqual('the result'); - }); - }); - - // Both the Infura and fetch middleware detect ETIMEDOUT errors and will - // automatically retry the request to the RPC endpoint in question, but each - // produces a different error if the number of retries is exhausted. - if (providerType === 'infura') { - it('causes a request to fail with a custom error if an "ETIMEDOUT" error is thrown while making the request to the RPC endpoint 5 times in a row', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { method }; - const errorMessage = 'ETIMEDOUT: Some message'; - - // 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.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - request, - blockParamIndex, - '0x100', - ), - error: errorMessage, - times: 5, - }); - - const promiseForResult = withNetworkClient( - { providerType }, - async ({ makeRpcCall, clock }) => { - return await waitForPromiseToBeFulfilledAfterRunningAllTimers( - makeRpcCall(request), - clock, - ); - }, - ); - - await expect(promiseForResult).rejects.toThrow( - buildInfuraClientRetriesExhaustedErrorMessage(errorMessage), - ); - }); - }); - } else { - it('produces an empty response if an "ETIMEDOUT" error is thrown while making the request to the RPC endpoint 5 times in a row', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { - method, - params: buildMockParams({ blockParam, blockParamIndex }), - }; - const errorMessage = 'ETIMEDOUT: Some message'; - - // 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.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - request, - blockParamIndex, - '0x100', - ), - error: errorMessage, - times: 5, - }); - - const promiseForResult = withNetworkClient( - { providerType }, - async ({ makeRpcCall, clock }) => { - return await waitForPromiseToBeFulfilledAfterRunningAllTimers( - makeRpcCall(request), - clock, - ); - }, - ); - - await expect(promiseForResult).rejects.toThrow( - buildJsonRpcEngineEmptyResponseErrorMessage(method), - ); - }); - }); - } - - // The Infura middleware treats a response that contains an ECONNRESET - // message as an innocuous error that is likely to disappear on a retry. The - // custom RPC middleware, on the other hand, does not specially handle this - // error. - if (providerType === 'infura') { - it('retries the request to the RPC endpoint up to 5 times if an "ECONNRESET" error is thrown while making the request, returning the successful result if there is one on the 5th try', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { - method, - params: buildMockParams({ blockParam, blockParamIndex }), - }; - - // 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. - // - // Here we have the request fail for the first 4 tries, then - // succeed on the 5th try. - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - request, - blockParamIndex, - '0x100', - ), - error: 'ECONNRESET: Some message', - times: 4, - }); - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - request, - blockParamIndex, - '0x100', - ), - response: { - result: 'the result', - httpStatus: 200, - }, - }); - - const result = await withNetworkClient( - { providerType }, - async ({ makeRpcCall, clock }) => { - return await waitForPromiseToBeFulfilledAfterRunningAllTimers( - makeRpcCall(request), - clock, - ); - }, - ); - - expect(result).toStrictEqual('the result'); - }); - }); - - it('causes a request to fail with a custom error if an "ECONNRESET" error is thrown while making the request to the RPC endpoint 5 times in a row', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { - method, - params: buildMockParams({ blockParam, blockParamIndex }), - }; - const errorMessage = 'ECONNRESET: Some message'; - - // 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.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - request, - blockParamIndex, - '0x100', - ), - error: errorMessage, - times: 5, - }); - - const promiseForResult = withNetworkClient( - { providerType }, - async ({ makeRpcCall, clock }) => { - return await waitForPromiseToBeFulfilledAfterRunningAllTimers( - makeRpcCall(request), - clock, - ); - }, - ); - - await expect(promiseForResult).rejects.toThrow( - buildInfuraClientRetriesExhaustedErrorMessage(errorMessage), - ); - }); - }); - } else { - it('does not retry the request to the RPC endpoint, but throws immediately, if an "ECONNRESET" error is thrown while making the request', async () => { - const customRpcUrl = 'http://example.com'; - - await withMockedCommunications( - { providerType, customRpcUrl }, - async (comms) => { - const request = { - method, - params: buildMockParams({ blockParam, blockParamIndex }), - }; - const errorMessage = 'ECONNRESET: Some message'; - - // 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.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - request, - blockParamIndex, - '0x100', - ), - error: errorMessage, - }); - - const promiseForResult = withNetworkClient( - { providerType, customRpcUrl }, - async ({ makeRpcCall }) => makeRpcCall(request), - ); - - await expect(promiseForResult).rejects.toThrow( - buildFetchFailedErrorMessage(customRpcUrl, errorMessage), - ); - }, - ); - }); - } - - // Both the Infura and fetch middleware will attempt to parse the response - // body as JSON, and if this step produces an error, both middleware will - // also attempt to retry the request. However, this error handling code is - // slightly different between the two. As the error in this case is a - // SyntaxError, the Infura middleware will catch it immediately, whereas the - // custom RPC middleware will catch it and re-throw a separate error, which - // it then catches later. - if (providerType === 'infura') { - it('retries the request to the RPC endpoint up to 5 times if a "SyntaxError" error is thrown while making the request, returning the successful result if there is one on the 5th try', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { - method, - params: buildMockParams({ blockParam, blockParamIndex }), - }; - - // 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. - // - // Here we have the request fail for the first 4 tries, then - // succeed on the 5th try. - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - request, - blockParamIndex, - '0x100', - ), - error: 'SyntaxError: Some message', - times: 4, - }); - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - request, - blockParamIndex, - '0x100', - ), - response: { - result: 'the result', - httpStatus: 200, - }, - }); - const result = await withNetworkClient( - { providerType }, - async ({ makeRpcCall, clock }) => { - return await waitForPromiseToBeFulfilledAfterRunningAllTimers( - makeRpcCall(request), - clock, - ); - }, - ); - - expect(result).toStrictEqual('the result'); - }); - }); - - it('causes a request to fail with a custom error if a "SyntaxError" error is thrown while making the request to the RPC endpoint 5 times in a row', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { - method, - params: buildMockParams({ blockParam, blockParamIndex }), - }; - const errorMessage = 'SyntaxError: Some message'; - - // 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.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - request, - blockParamIndex, - '0x100', - ), - error: errorMessage, - times: 5, - }); - - const promiseForResult = withNetworkClient( - { providerType }, - async ({ makeRpcCall, clock }) => { - return await waitForPromiseToBeFulfilledAfterRunningAllTimers( - makeRpcCall(request), - clock, - ); - }, - ); - - await expect(promiseForResult).rejects.toThrow( - buildInfuraClientRetriesExhaustedErrorMessage(errorMessage), - ); - }); - }); - - it('does not retry the request to the RPC endpoint, but throws immediately, if a "failed to parse response body" error is thrown while making the request', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { - method, - params: buildMockParams({ blockParam, blockParamIndex }), - }; - const errorMessage = 'failed to parse response body: Some message'; - - // 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.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - request, - blockParamIndex, - '0x100', - ), - error: errorMessage, - }); - - const promiseForResult = withNetworkClient( - { providerType, infuraNetwork: comms.infuraNetwork }, - async ({ makeRpcCall }) => makeRpcCall(request), - ); - - await expect(promiseForResult).rejects.toThrow( - buildFetchFailedErrorMessage(comms.rpcUrl, errorMessage), - ); - }); - }); - } else { - it('does not retry the request to the RPC endpoint, but throws immediately, if a "SyntaxError" error is thrown while making the request', async () => { - const customRpcUrl = 'http://example.com'; - - await withMockedCommunications( - { providerType, customRpcUrl }, - async (comms) => { - const request = { - method, - params: buildMockParams({ blockParam, blockParamIndex }), - }; - const errorMessage = 'SyntaxError: Some message'; - - // 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.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - request, - blockParamIndex, - '0x100', - ), - error: errorMessage, - }); - - const promiseForResult = withNetworkClient( - { providerType, customRpcUrl }, - async ({ makeRpcCall }) => makeRpcCall(request), - ); - - await expect(promiseForResult).rejects.toThrow( - buildFetchFailedErrorMessage(customRpcUrl, errorMessage), - ); - }, - ); - }); - - it('retries the request to the RPC endpoint up to 5 times if a "failed to parse response body" error is thrown while making the request, returning the successful result if there is one on the 5th try', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { - method, - params: buildMockParams({ blockParam, blockParamIndex }), - }; - - // 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. - // - // Here we have the request fail for the first 4 tries, then - // succeed on the 5th try. - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - request, - blockParamIndex, - '0x100', - ), - error: 'failed to parse response body: Some message', - times: 4, - }); - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - request, - blockParamIndex, - '0x100', - ), - response: { - result: 'the result', - httpStatus: 200, - }, - }); - - const result = await withNetworkClient( - { providerType }, - async ({ makeRpcCall, clock }) => { - return await waitForPromiseToBeFulfilledAfterRunningAllTimers( - makeRpcCall(request), - clock, - ); - }, - ); - - expect(result).toStrictEqual('the result'); - }); - }); - - it('produces an empty response if a "failed to parse response body" error is thrown while making the request to the RPC endpoint 5 times in a row', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { - method, - params: buildMockParams({ blockParam, blockParamIndex }), - }; - const errorMessage = 'failed to parse response body: some message'; - - // 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.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - request, - blockParamIndex, - '0x100', - ), - error: errorMessage, - times: 5, - }); - const promiseForResult = withNetworkClient( - { providerType }, - async ({ makeRpcCall, clock }) => { - return await waitForPromiseToBeFulfilledAfterRunningAllTimers( - makeRpcCall(request), - clock, - ); - }, - ); - - await expect(promiseForResult).rejects.toThrow( - buildJsonRpcEngineEmptyResponseErrorMessage(method), - ); - }); - }); - } - - // Only the custom RPC middleware will detect a "Failed to fetch" error and - // attempt to retry the request to the RPC endpoint; the Infura middleware - // does not. - if (providerType === 'infura') { - it('does not retry the request to the RPC endpoint, but throws immediately, if a "Failed to fetch" error is thrown while making the request', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { - method, - params: buildMockParams({ blockParam, blockParamIndex }), - }; - const errorMessage = 'Failed to fetch: Some message'; - - // 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.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - request, - blockParamIndex, - '0x100', - ), - error: errorMessage, - }); - - const promiseForResult = withNetworkClient( - { providerType, infuraNetwork: comms.infuraNetwork }, - async ({ makeRpcCall }) => makeRpcCall(request), - ); - - await expect(promiseForResult).rejects.toThrow( - buildFetchFailedErrorMessage(comms.rpcUrl, errorMessage), - ); - }); - }); - } else { - it('retries the request to the RPC endpoint up to 5 times if a "Failed to fetch" error is thrown while making the request, returning the successful result if there is one on the 5th try', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { - method, - params: buildMockParams({ blockParam, blockParamIndex }), - }; - - // 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. - // - // Here we have the request fail for the first 4 tries, then - // succeed on the 5th try. - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - request, - blockParamIndex, - '0x100', - ), - error: 'Failed to fetch: Some message', - times: 4, - }); - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - request, - blockParamIndex, - '0x100', - ), - response: { - result: 'the result', - httpStatus: 200, - }, - }); - - const result = await withNetworkClient( - { providerType }, - async ({ makeRpcCall, clock }) => { - return await waitForPromiseToBeFulfilledAfterRunningAllTimers( - makeRpcCall(request), - clock, - ); - }, - ); - - expect(result).toStrictEqual('the result'); - }); - }); - - it('produces an empty response if a "Failed to fetch" error is thrown while making the request to the RPC endpoint 5 times in a row', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { method }; - const errorMessage = 'Failed to fetch: some message'; - - // 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.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - request, - blockParamIndex, - '0x100', - ), - error: errorMessage, - times: 5, - }); - const promiseForResult = withNetworkClient( - { providerType }, - async ({ makeRpcCall, clock }) => { - return await waitForPromiseToBeFulfilledAfterRunningAllTimers( - makeRpcCall(request), - clock, - ); - }, - ); - - await expect(promiseForResult).rejects.toThrow( - buildJsonRpcEngineEmptyResponseErrorMessage(method), - ); - }); - }); - } - }); - - describe.each([ - ['given a block tag of "earliest"', 'earliest', 'earliest'], - ['given a block number', 'block number', '0x100'], - ])('%s', (_desc, blockParamType, blockParam) => { - it('does not hit the RPC endpoint more than once for identical requests', async () => { - const requests = [ - { - method, - params: buildMockParams({ blockParamIndex, blockParam }), - }, - { - method, - params: buildMockParams({ blockParamIndex, blockParam }), - }, - ]; - const mockResults = ['first result', 'second result']; - - await withMockedCommunications({ providerType }, 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.mockRpcCall({ - request: requests[0], - response: { result: mockResults[0] }, - }); - - const results = await withNetworkClient( - { providerType }, - ({ makeRpcCallsInSeries }) => makeRpcCallsInSeries(requests), - ); - - expect(results).toStrictEqual([mockResults[0], mockResults[0]]); - }); - }); - - for (const paramIndex of [...Array(numberOfParameters).keys()]) { - if (paramIndex === blockParamIndex) { - // testing changes in block param is covered under later tests - continue; - } - it(`does not reuse the result of a previous request if parameter at index "${paramIndex}" differs`, async () => { - const firstMockParams = [ - ...new Array(numberOfParameters).fill('some value'), - ]; - firstMockParams[blockParamIndex] = blockParam; - const secondMockParams = firstMockParams.slice(); - secondMockParams[paramIndex] = 'another value'; - const requests = [ - { - method, - params: firstMockParams, - }, - { method, params: secondMockParams }, - ]; - const mockResults = ['first result', 'second result']; - - await withMockedCommunications({ providerType }, 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.mockRpcCall({ - request: requests[0], - response: { result: mockResults[0] }, - }); - comms.mockRpcCall({ - request: requests[1], - response: { result: mockResults[1] }, - }); - - const results = await withNetworkClient( - { providerType }, - ({ makeRpcCallsInSeries }) => makeRpcCallsInSeries(requests), - ); - - expect(results).toStrictEqual([mockResults[0], mockResults[1]]); - }); - }); - } - - it('reuses the result of a previous request even if the latest block number was updated since', async () => { - const requests = [ - { - method, - params: buildMockParams({ blockParamIndex, blockParam }), - }, - { - method, - params: buildMockParams({ blockParamIndex, blockParam }), - }, - ]; - const mockResults = ['first result', 'second result']; - - await withMockedCommunications({ providerType }, 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.mockRpcCall({ - request: requests[0], - response: { result: mockResults[0] }, - }); - comms.mockNextBlockTrackerRequest({ blockNumber: '0x2' }); - comms.mockRpcCall({ - request: requests[1], - response: { result: mockResults[1] }, - }); - - const results = await withNetworkClient( - { providerType }, - 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]]); - }); - }); - - if (blockParamType === 'earliest') { - it('treats "0x00" as a synonym for "earliest"', async () => { - const requests = [ - { - method, - params: buildMockParams({ blockParamIndex, blockParam }), - }, - { - method, - params: buildMockParams({ blockParamIndex, blockParam: '0x00' }), - }, - ]; - const mockResults = ['first result', 'second result']; - - await withMockedCommunications({ providerType }, 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.mockRpcCall({ - request: requests[0], - response: { result: mockResults[0] }, - }); - - const results = await withNetworkClient( - { providerType }, - ({ makeRpcCallsInSeries }) => makeRpcCallsInSeries(requests), - ); - - expect(results).toStrictEqual([mockResults[0], mockResults[0]]); - }); - }); - - for (const emptyValue of [null, undefined, '\u003cnil\u003e']) { - it(`does not retry an empty response of "${emptyValue}"`, async () => { - const request = { - method, - params: buildMockParams({ blockParamIndex, blockParam }), - }; - const mockResult = emptyValue; - - await withMockedCommunications({ providerType }, 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.mockRpcCall({ - request, - response: { result: mockResult }, - }); - - const result = await withNetworkClient( - { providerType }, - ({ makeRpcCall }) => makeRpcCall(request), - ); - - expect(result).toStrictEqual(mockResult); - }); - }); - - it(`does not reuse the result of a previous request if it was "${emptyValue}"`, async () => { - const requests = [ - { - method, - params: buildMockParams({ blockParamIndex, blockParam }), - }, - { - method, - params: buildMockParams({ blockParamIndex, blockParam }), - }, - ]; - const mockResults = [emptyValue, 'some result']; - - await withMockedCommunications({ providerType }, 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.mockRpcCall({ - request: requests[0], - response: { result: mockResults[0] }, - }); - comms.mockRpcCall({ - request: requests[1], - response: { result: mockResults[1] }, - }); - - const results = await withNetworkClient( - { providerType }, - ({ makeRpcCallsInSeries }) => makeRpcCallsInSeries(requests), - ); - - expect(results).toStrictEqual(mockResults); - }); - }); - } - } - - 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 withMockedCommunications({ providerType }, async (comms) => { - const requests = [ - { - method, - params: buildMockParams({ blockParamIndex, blockParam: '0x100' }), - }, - { - method, - params: buildMockParams({ blockParamIndex, blockParam: '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.mockRpcCall({ - request: requests[0], - response: { result: 'first result' }, - }); - comms.mockRpcCall({ - request: requests[1], - response: { result: 'second result' }, - }); - - const results = await withNetworkClient( - { providerType }, - ({ makeRpcCallsInSeries }) => makeRpcCallsInSeries(requests), - ); - - expect(results).toStrictEqual(['first result', 'second result']); - }); - }); - - describe.each( - [ - ['less than the current block number', '0x200'], - ['equal to the curent block number', '0x100'], - ] as any, - '%s', - (_nestedDesc: string, currentBlockNumber: string) => { - it('makes an additional request to the RPC endpoint', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { - method, - // Note that `blockParam` is `0x100` here - params: buildMockParams({ blockParamIndex, blockParam }), - }; - - // The first time a block-cacheable request is made, the latest - // block number is retrieved through the block tracker first. - comms.mockNextBlockTrackerRequest({ - blockNumber: currentBlockNumber, - }); - comms.mockRpcCall({ - request, - response: { result: 'the result' }, - }); - - const result = await withNetworkClient( - { providerType }, - ({ makeRpcCall }) => makeRpcCall(request), - ); - - expect(result).toStrictEqual('the result'); - }); - }); - - for (const emptyValue of [null, undefined, '\u003cnil\u003e']) { - if (providerType === 'infura') { - it(`retries up to 10 times if a "${emptyValue}" response is returned, returning successful non-empty response if there is one on the 10th try`, async () => { - const request = { - method, - // Note that `blockParam` is `0x100` here - params: buildMockParams({ blockParamIndex, blockParam }), - }; - - await withMockedCommunications( - { providerType }, - async (comms) => { - // The first time a block-cacheable request is made, the latest block - // number is retrieved through the block tracker first. - comms.mockNextBlockTrackerRequest({ - blockNumber: currentBlockNumber, - }); - comms.mockRpcCall({ - request, - response: { result: emptyValue }, - times: 9, - }); - comms.mockRpcCall({ - request, - response: { result: 'some value' }, - }); - - const result = await withNetworkClient( - { providerType }, - ({ makeRpcCall, clock }) => - waitForPromiseToBeFulfilledAfterRunningAllTimers( - makeRpcCall(request), - clock, - ), - ); - - expect(result).toStrictEqual('some value'); - }, - ); - }); - - it(`retries up to 10 times if a "${emptyValue}" response is returned, failing after the 10th try`, async () => { - const request = { - method, - // Note that `blockParam` is `0x100` here - params: buildMockParams({ blockParamIndex, blockParam }), - }; - const mockResult = emptyValue; - - await withMockedCommunications( - { providerType }, - async (comms) => { - // The first time a block-cacheable request is made, the latest block - // number is retrieved through the block tracker first. - comms.mockNextBlockTrackerRequest({ - blockNumber: currentBlockNumber, - }); - comms.mockRpcCall({ - request, - response: { result: mockResult }, - times: 10, - }); - - const promiseForResult = withNetworkClient( - { providerType }, - ({ makeRpcCall, clock }) => - waitForPromiseToBeFulfilledAfterRunningAllTimers( - makeRpcCall(request), - clock, - ), - ); - - await expect(promiseForResult).rejects.toThrow( - 'RetryOnEmptyMiddleware - retries exhausted', - ); - }, - ); - }); - } else { - it(`does not retry an empty response of "${emptyValue}"`, async () => { - const request = { - method, - // Note that `blockParam` is `0x100` here - params: buildMockParams({ blockParamIndex, blockParam }), - }; - const mockResult = emptyValue; - - await withMockedCommunications( - { providerType }, - async (comms) => { - // The first time a block-cacheable request is made, the latest block - // number is retrieved through the block tracker first. - comms.mockNextBlockTrackerRequest({ - blockNumber: currentBlockNumber, - }); - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - request, - blockParamIndex, - '0x100', - ), - response: { result: mockResult }, - }); - - const result = await withNetworkClient( - { providerType }, - ({ makeRpcCall }) => makeRpcCall(request), - ); - - expect(result).toStrictEqual(mockResult); - }, - ); - }); - - it(`does not reuse the result of a previous request if it was "${emptyValue}"`, async () => { - const requests = [ - { - method, - // Note that `blockParam` is `0x100` here - params: buildMockParams({ blockParamIndex, blockParam }), - }, - { - method, - // Note that `blockParam` is `0x100` here - params: buildMockParams({ blockParamIndex, blockParam }), - }, - ]; - const mockResults = [emptyValue, { blockHash: '0x100' }]; - - await withMockedCommunications( - { providerType }, - async (comms) => { - // The first time a block-cacheable request is made, the latest block - // number is retrieved through the block tracker first. - comms.mockNextBlockTrackerRequest({ - blockNumber: currentBlockNumber, - }); - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - requests[0], - blockParamIndex, - '0x100', - ), - response: { result: mockResults[0] }, - }); - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - requests[1], - blockParamIndex, - '0x100', - ), - response: { result: mockResults[1] }, - }); - - const results = await withNetworkClient( - { providerType }, - ({ makeRpcCallsInSeries }) => - makeRpcCallsInSeries(requests), - ); - - expect(results).toStrictEqual(mockResults); - }, - ); - }); - } - } - }, - ); - - describe('greater than the current block number', () => { - it('makes an additional request to the RPC endpoint', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { - method, - // Note that `blockParam` is `0x100` here - params: buildMockParams({ blockParamIndex, blockParam }), - }; - - // The first time a block-cacheable request is made, the latest - // block number is retrieved through the block tracker first. - comms.mockNextBlockTrackerRequest({ blockNumber: '0x42' }); - comms.mockRpcCall({ - request, - response: { result: 'the result' }, - }); - - const result = await withNetworkClient( - { providerType }, - ({ makeRpcCall }) => makeRpcCall(request), - ); - - expect(result).toStrictEqual('the result'); - }); - }); - - for (const emptyValue of [null, undefined, '\u003cnil\u003e']) { - it(`does not retry an empty response of "${emptyValue}"`, async () => { - const request = { - method, - // Note that `blockParam` is `0x100` here - params: buildMockParams({ blockParamIndex, blockParam }), - }; - const mockResult = emptyValue; - - await withMockedCommunications({ providerType }, async (comms) => { - // The first time a block-cacheable request is made, the latest block - // number is retrieved through the block tracker first. - comms.mockNextBlockTrackerRequest({ blockNumber: '0x42' }); - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - request, - blockParamIndex, - '0x100', - ), - response: { result: mockResult }, - }); - - const result = await withNetworkClient( - { providerType }, - ({ makeRpcCall }) => makeRpcCall(request), - ); - - expect(result).toStrictEqual(mockResult); - }); - }); - - it(`does not reuse the result of a previous request if it was "${emptyValue}"`, async () => { - const requests = [ - { - method, - // Note that `blockParam` is `0x100` here - params: buildMockParams({ blockParamIndex, blockParam }), - }, - { - method, - // Note that `blockParam` is `0x100` here - params: buildMockParams({ blockParamIndex, blockParam }), - }, - ]; - const mockResults = [emptyValue, { blockHash: '0x100' }]; - - await withMockedCommunications({ providerType }, async (comms) => { - // The first time a block-cacheable request is made, the latest block - // number is retrieved through the block tracker first. - comms.mockNextBlockTrackerRequest({ blockNumber: '0x42' }); - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - requests[0], - blockParamIndex, - '0x100', - ), - response: { result: mockResults[0] }, - }); - comms.mockRpcCall({ - request: buildRequestWithReplacedBlockParam( - requests[1], - blockParamIndex, - '0x100', - ), - response: { result: mockResults[1] }, - }); - - const results = await withNetworkClient( - { providerType }, - ({ makeRpcCallsInSeries }) => makeRpcCallsInSeries(requests), - ); - - expect(results).toStrictEqual(mockResults); - }); - }); - } - }); - } - }); - - describe('given a block tag of "pending"', () => { - const params = buildMockParams({ blockParamIndex, blockParam: 'pending' }); - - it('hits the RPC endpoint on all calls and does not cache anything', async () => { - const requests = [ - { method, params }, - { method, params }, - ]; - const mockResults = ['first result', 'second result']; - - await withMockedCommunications({ providerType }, 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.mockRpcCall({ - request: requests[0], - response: { result: mockResults[0] }, - }); - comms.mockRpcCall({ - request: requests[1], - response: { result: mockResults[1] }, - }); - - const results = await withNetworkClient( - { providerType }, - ({ makeRpcCallsInSeries }) => makeRpcCallsInSeries(requests), - ); - - expect(results).toStrictEqual(mockResults); - }); - }); - }); -} diff --git a/app/scripts/controllers/network/provider-api-tests/helpers.ts b/app/scripts/controllers/network/provider-api-tests/helpers.ts deleted file mode 100644 index 104715d13..000000000 --- a/app/scripts/controllers/network/provider-api-tests/helpers.ts +++ /dev/null @@ -1,543 +0,0 @@ -import nock, { Scope as NockScope } from 'nock'; -import sinon from 'sinon'; -import type { JSONRPCResponse } from '@json-rpc-specification/meta-schema'; -import EthQuery from 'eth-query'; -import { Hex } from '@metamask/utils'; -import { BuiltInInfuraNetwork } from '../../../../../shared/constants/network'; -import { - createNetworkClient, - NetworkClientType, -} from '../create-network-client'; - -/** - * A dummy value for the `infuraProjectId` option that `createInfuraClient` - * needs. (Infura should not be hit during tests, but just in case, this should - * not refer to a real project ID.) - */ -const MOCK_INFURA_PROJECT_ID = 'abc123'; - -/** - * A dummy value for the `rpcUrl` option that `createJsonRpcClient` needs. (This - * should not be hit during tests, but just in case, this should also not refer - * to a real Infura URL.) - */ -const MOCK_RPC_URL = 'http://foo.com'; - -/** - * A default value for the `eth_blockNumber` request that the block tracker - * makes. - */ -const DEFAULT_LATEST_BLOCK_NUMBER = '0x42'; - -/** - * A reference to the original `setTimeout` function so that we can use it even - * when using fake timers. - */ -const originalSetTimeout = setTimeout; - -/** - * 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 args - The arguments that `console.log` takes. - */ -function debug(...args: any) { - if (process.env.DEBUG_PROVIDER_TESTS === '1') { - console.log(...args); - } -} - -/** - * Builds a Nock scope object for mocking provider requests. - * - * @param rpcUrl - The URL of the RPC endpoint. - * @returns The nock scope. - */ -function buildScopeForMockingRequests(rpcUrl: string): NockScope { - return nock(rpcUrl).filteringRequestBody((body) => { - debug('Nock Received Request: ', body); - return body; - }); -} - -type Request = { method: string; params?: any[] }; -type Response = { - id?: number | string; - jsonrpc?: '2.0'; - error?: any; - result?: any; - httpStatus?: number; -}; -type ResponseBody = { body: JSONRPCResponse }; -type BodyOrResponse = ResponseBody | Response; -type CurriedMockRpcCallOptions = { - request: Request; - // The response data. - response?: BodyOrResponse; - /** - * An error to throw while making the request. - * Takes precedence over `response`. - */ - error?: Error | string; - /** - * The amount of time that should pass before the - * request resolves with the response. - */ - delay?: number; - /** - * The number of times that the request is - * expected to be made. - */ - times?: number; -}; - -type MockRpcCallOptions = { - // A nock scope (a set of mocked requests scoped to a certain base URL). - nockScope: nock.Scope; -} & CurriedMockRpcCallOptions; - -type MockRpcCallResult = nock.Interceptor | nock.Scope; - -/** - * Mocks a JSON-RPC request sent to the provider with the given response. - * Provider type is inferred from the base url set on the nockScope. - * - * @param args - The arguments. - * @param args.nockScope - A nock scope (a set of mocked requests scoped to a - * certain base URL). - * @param args.request - The request data. - * @param args.response - Information concerning the response that the request - * should have. If a `body` property is present, this is taken as the complete - * response body. If an `httpStatus` property is present, then it is taken as - * the HTTP status code to respond with. Properties other than these two are - * used to build a complete response body (including `id` and `jsonrpc` - * properties). - * @param args.error - An error to throw while making the request. Takes - * precedence over `response`. - * @param args.delay - The amount of time that should pass before the request - * resolves with the response. - * @param args.times - The number of times that the request is expected to be - * made. - * @returns The nock scope. - */ -function mockRpcCall({ - nockScope, - request, - response, - error, - delay, - times, -}: MockRpcCallOptions): MockRpcCallResult { - // 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; - let httpStatus = 200; - let completeResponse: JSONRPCResponse = { id: 2, jsonrpc: '2.0' }; - if (response !== undefined) { - if ('body' in response) { - completeResponse = response.body; - } else { - if (response.error) { - completeResponse.error = response.error; - } else { - completeResponse.result = response.result; - } - if (response.httpStatus) { - httpStatus = response.httpStatus; - } - } - } - /* @ts-expect-error The types for Nock do not include `basePath` in the interface for Nock.Scope. */ - const url = new URL(nockScope.basePath).hostname.match(/(\.|^)infura.io$/u) - ? `/v3/${MOCK_INFURA_PROJECT_ID}` - : '/'; - - debug('Mocking request:', { - url, - method, - params, - response, - error, - ...rest, - times, - }); - - let nockRequest = nockScope.post(url, { - id: /\d*/u, - jsonrpc: '2.0', - method, - params, - ...rest, - }); - - if (delay !== undefined) { - nockRequest = nockRequest.delay(delay); - } - - if (times !== undefined) { - nockRequest = nockRequest.times(times); - } - - if (error !== undefined) { - return nockRequest.replyWithError(error); - } else if (completeResponse !== undefined) { - return nockRequest.reply(httpStatus, (_, requestBody: any) => { - if (response !== undefined && !('body' in response)) { - if (response.id === undefined) { - completeResponse.id = requestBody.id; - } else { - completeResponse.id = response.id; - } - } - debug('Nock returning Response', completeResponse); - return completeResponse; - }); - } - return nockRequest; -} - -type MockBlockTrackerRequestOptions = { - /** - * A nock scope (a set of mocked requests scoped to a certain base url). - */ - nockScope: NockScope; - /** - * The block number that the block tracker should report, as a 0x-prefixed hex - * string. - */ - blockNumber: string; -}; - -/** - * Mocks the next request for the latest block that the block tracker will make. - * - * @param args - The arguments. - * @param args.nockScope - A nock scope (a set of mocked requests scoped to a - * certain base URL). - * @param args.blockNumber - The block number that the block tracker should - * report, as a 0x-prefixed hex string. - */ -function mockNextBlockTrackerRequest({ - nockScope, - blockNumber = DEFAULT_LATEST_BLOCK_NUMBER, -}: MockBlockTrackerRequestOptions) { - mockRpcCall({ - nockScope, - request: { method: 'eth_blockNumber', params: [] }, - response: { result: blockNumber }, - }); -} - -/** - * Mocks all requests for the latest block that the block tracker will make. - * - * @param args - The arguments. - * @param args.nockScope - A nock scope (a set of mocked requests scoped to a - * certain base URL). - * @param args.blockNumber - The block number that the block tracker should - * report, as a 0x-prefixed hex string. - */ -async function mockAllBlockTrackerRequests({ - nockScope, - blockNumber = DEFAULT_LATEST_BLOCK_NUMBER, -}: MockBlockTrackerRequestOptions) { - const result = await mockRpcCall({ - nockScope, - request: { method: 'eth_blockNumber', params: [] }, - response: { result: blockNumber }, - }); - - if ('persist' in result) { - result.persist(); - } -} - -/** - * Makes a JSON-RPC call through the given eth-query object. - * - * @param ethQuery - The eth-query object. - * @param request - The request data. - * @returns 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: EthQuery, request: 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); - } - }); - }); -} - -export type ProviderType = 'infura' | 'custom'; - -export type MockOptions = { - infuraNetwork?: BuiltInInfuraNetwork; - providerType: ProviderType; - customRpcUrl?: string; - customChainId?: Hex; -}; - -export type MockCommunications = { - mockNextBlockTrackerRequest: (options?: any) => void; - mockAllBlockTrackerRequests: (options?: any) => void; - mockRpcCall: (options: CurriedMockRpcCallOptions) => MockRpcCallResult; - rpcUrl: string; - infuraNetwork: BuiltInInfuraNetwork; -}; - -/** - * Sets up request mocks for requests to the provider. - * - * @param options - An options bag. - * @param options.providerType - The type of network client being tested. - * @param options.infuraNetwork - The name of the Infura network being tested, - * assuming that `providerType` is "infura" (default: "mainnet"). - * @param options.customRpcUrl - The URL of the custom RPC endpoint, assuming - * that `providerType` is "custom". - * @param fn - A function which will be called with an object that allows - * interaction with the network client. - * @returns The return value of the given function. - */ -export async function withMockedCommunications( - { - providerType, - infuraNetwork = 'mainnet', - customRpcUrl = MOCK_RPC_URL, - }: MockOptions, - fn: (comms: MockCommunications) => Promise, -) { - if (providerType !== 'infura' && providerType !== 'custom') { - throw new Error( - `providerType must be either "infura" or "custom", was "${providerType}" instead`, - ); - } - - const rpcUrl = - providerType === 'infura' - ? `https://${infuraNetwork}.infura.io` - : customRpcUrl; - const nockScope = buildScopeForMockingRequests(rpcUrl); - const curriedMockNextBlockTrackerRequest = (localOptions: any) => - mockNextBlockTrackerRequest({ nockScope, ...localOptions }); - const curriedMockAllBlockTrackerRequests = (localOptions: any) => - mockAllBlockTrackerRequests({ nockScope, ...localOptions }); - const curriedMockRpcCall = (localOptions: any) => - mockRpcCall({ nockScope, ...localOptions }); - - const comms = { - mockNextBlockTrackerRequest: curriedMockNextBlockTrackerRequest, - mockAllBlockTrackerRequests: curriedMockAllBlockTrackerRequests, - mockRpcCall: curriedMockRpcCall, - rpcUrl, - infuraNetwork, - }; - - try { - return await fn(comms); - } finally { - nock.isDone(); - nock.cleanAll(); - } -} - -type MockNetworkClient = { - blockTracker: any; - clock: sinon.SinonFakeTimers; - makeRpcCall: (request: Request) => Promise; - makeRpcCallsInSeries: (requests: Request[]) => Promise; -}; - -/** - * Some middleware contain logic which retries the request if some condition - * applies. This retrying always happens out of band via `setTimeout`, and - * because we are stubbing time via Jest's fake timers, we have to manually - * advance the clock so that the `setTimeout` handlers get fired. We don't know - * when these timers will get created, however, so we have to keep advancing - * timers until the request has been made an appropriate number of times. - * Unfortunately we don't have a good way to know how many times a request has - * been retried, but the good news is that the middleware won't end, and thus - * the promise which the RPC call returns won't get fulfilled, until all retries - * have been made. - * - * @param promise - The promise which is returned by the RPC call. - * @param clock - A Sinon clock object which can be used to advance to the next - * `setTimeout` handler. - */ -export async function waitForPromiseToBeFulfilledAfterRunningAllTimers( - promise: any, - clock: any, -) { - let hasPromiseBeenFulfilled = false; - let numTimesClockHasBeenAdvanced = 0; - - promise - .catch((error: any) => { - // This is used to silence Node.js warnings about the rejection - // being handled asynchronously. The error is handled later when - // `promise` is awaited, but we log it here anyway in case it gets - // swallowed. - debug(error); - }) - .finally(() => { - hasPromiseBeenFulfilled = true; - }); - - // `hasPromiseBeenFulfilled` is modified asynchronously. - /* eslint-disable-next-line no-unmodified-loop-condition */ - while (!hasPromiseBeenFulfilled && numTimesClockHasBeenAdvanced < 15) { - clock.runAll(); - await new Promise((resolve) => originalSetTimeout(resolve, 10)); - numTimesClockHasBeenAdvanced += 1; - } - - return promise; -} - -/** - * Builds a provider from the middleware (for the provider type) 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 options - An options bag. - * @param options.providerType - The type of network client being tested. - * @param options.infuraNetwork - The name of the Infura network being tested, - * assuming that `providerType` is "infura" (default: "mainnet"). - * @param options.customRpcUrl - The URL of the custom RPC endpoint, assuming - * that `providerType` is "custom". - * @param options.customChainId - The chain id belonging to the custom RPC - * endpoint, assuming that `providerType` is "custom" (default: "0x1"). - * @param fn - A function which will be called with an object that allows - * interaction with the network client. - * @returns The return value of the given function. - */ -export async function withNetworkClient( - { - providerType, - infuraNetwork = 'mainnet', - customRpcUrl = MOCK_RPC_URL, - customChainId = '0x1', - }: MockOptions, - fn: (client: MockNetworkClient) => Promise, -) { - if (providerType !== 'infura' && providerType !== 'custom') { - throw new Error( - `providerType must be either "infura" or "custom", was "${providerType}" instead`, - ); - } - - // 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(); - - // The JSON-RPC client wraps `eth_estimateGas` so that it takes 2 seconds longer - // than it usually would to complete. Or at least it should — this doesn't - // appear to be working correctly. Unset `IN_TEST` on `process.env` to prevent - // this behavior. - const inTest = process.env.IN_TEST; - delete process.env.IN_TEST; - const clientUnderTest = - providerType === 'infura' - ? createNetworkClient({ - network: infuraNetwork, - infuraProjectId: MOCK_INFURA_PROJECT_ID, - type: NetworkClientType.Infura, - }) - : createNetworkClient({ - chainId: customChainId, - rpcUrl: customRpcUrl, - type: NetworkClientType.Custom, - }); - process.env.IN_TEST = inTest; - - const { provider, blockTracker } = clientUnderTest; - - const ethQuery = new EthQuery(provider); - const curriedMakeRpcCall = (request: Request) => - makeRpcCall(ethQuery, request); - const makeRpcCallsInSeries = async (requests: Request[]) => { - const responses = []; - for (const request of requests) { - responses.push(await curriedMakeRpcCall(request)); - } - return responses; - }; - - const client = { - blockTracker, - clock, - makeRpcCall: curriedMakeRpcCall, - makeRpcCallsInSeries, - }; - - try { - return await fn(client); - } finally { - await blockTracker.destroy(); - - clock.restore(); - } -} - -type BuildMockParamsOptions = { - // The block parameter value to set. - blockParam: any; - // The index of the block parameter. - blockParamIndex: number; -}; - -/** - * Build mock parameters for a JSON-RPC call. - * - * The string 'some value' is used as the default value for each entry. The - * block parameter index determines the number of parameters to generate. - * - * The block parameter can be set to a custom value. If no value is given, it - * is set as undefined. - * - * @param args - Arguments. - * @param args.blockParamIndex - The index of the block parameter. - * @param args.blockParam - The block parameter value to set. - * @returns The mock params. - */ -export function buildMockParams({ - blockParam, - blockParamIndex, -}: BuildMockParamsOptions) { - const params = new Array(blockParamIndex).fill('some value'); - params[blockParamIndex] = blockParam; - - return params; -} - -/** - * Returns a partial JSON-RPC request object, with the "block" param replaced - * with the given value. - * - * @param request - The request object. - * @param request.method - The request method. - * @param request.params - The request params. - * @param blockParamIndex - The index within the `params` array of the block - * param. - * @param blockParam - The desired block param value. - * @returns The updated request object. - */ -export function buildRequestWithReplacedBlockParam( - { method, params = [] }: Request, - blockParamIndex: number, - blockParam: any, -) { - const updatedParams = params.slice(); - updatedParams[blockParamIndex] = blockParam; - return { method, params: updatedParams }; -} diff --git a/app/scripts/controllers/network/provider-api-tests/no-block-param.ts b/app/scripts/controllers/network/provider-api-tests/no-block-param.ts deleted file mode 100644 index 662c7fac9..000000000 --- a/app/scripts/controllers/network/provider-api-tests/no-block-param.ts +++ /dev/null @@ -1,977 +0,0 @@ -/* eslint-disable jest/require-top-level-describe, jest/no-export */ - -import { - ProviderType, - waitForPromiseToBeFulfilledAfterRunningAllTimers, - withMockedCommunications, - withNetworkClient, -} from './helpers'; -import { - buildFetchFailedErrorMessage, - buildInfuraClientRetriesExhaustedErrorMessage, - buildJsonRpcEngineEmptyResponseErrorMessage, -} from './shared-tests'; - -type TestsForRpcMethodAssumingNoBlockParamOptions = { - providerType: ProviderType; - numberOfParameters: number; -}; - -/** - * 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. - * @param additionalArgs - Additional arguments. - * @param additionalArgs.numberOfParameters - The number of parameters supported by the method under test. - * @param additionalArgs.providerType - The type of provider being tested; - * either `infura` or `custom` (default: "infura"). - */ -export function testsForRpcMethodAssumingNoBlockParam( - method: string, - { - numberOfParameters, - providerType, - }: TestsForRpcMethodAssumingNoBlockParamOptions, -) { - if (providerType !== 'infura' && providerType !== 'custom') { - throw new Error( - `providerType must be either "infura" or "custom", was "${providerType}" instead`, - ); - } - - it('does not hit the RPC endpoint more than once for identical requests', async () => { - const requests = [{ method }, { method }]; - const mockResults = ['first result', 'second result']; - - await withMockedCommunications({ providerType }, 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.mockRpcCall({ - request: requests[0], - response: { result: mockResults[0] }, - }); - - const results = await withNetworkClient( - { providerType }, - ({ makeRpcCallsInSeries }) => makeRpcCallsInSeries(requests), - ); - - expect(results).toStrictEqual([mockResults[0], mockResults[0]]); - }); - }); - - for (const paramIndex of [...Array(numberOfParameters).keys()]) { - it(`does not reuse the result of a previous request if parameter at index "${paramIndex}" differs`, async () => { - const firstMockParams = [ - ...new Array(numberOfParameters).fill('some value'), - ]; - const secondMockParams = firstMockParams.slice(); - secondMockParams[paramIndex] = 'another value'; - const requests = [ - { - method, - params: firstMockParams, - }, - { method, params: secondMockParams }, - ]; - const mockResults = ['some result', 'another result']; - - await withMockedCommunications({ providerType }, 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.mockRpcCall({ - request: requests[0], - response: { result: mockResults[0] }, - }); - comms.mockRpcCall({ - request: requests[1], - response: { result: mockResults[1] }, - }); - - const results = await withNetworkClient( - { providerType }, - ({ makeRpcCallsInSeries }) => makeRpcCallsInSeries(requests), - ); - - expect(results).toStrictEqual([mockResults[0], mockResults[1]]); - }); - }); - } - - it('hits the RPC endpoint 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 withMockedCommunications({ providerType }, 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.mockRpcCall({ - request: requests[0], - response: { result: mockResults[0] }, - }); - comms.mockNextBlockTrackerRequest({ blockNumber: '0x2' }); - comms.mockRpcCall({ - request: requests[1], - response: { result: mockResults[1] }, - }); - - const results = await withNetworkClient( - { providerType }, - 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); - }); - }); - - for (const emptyValue of [null, undefined, '\u003cnil\u003e']) { - it(`does not retry an empty response of "${emptyValue}"`, async () => { - const request = { method }; - const mockResult = emptyValue; - - await withMockedCommunications({ providerType }, 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.mockRpcCall({ - request, - response: { result: mockResult }, - }); - - const result = await withNetworkClient( - { providerType }, - ({ makeRpcCall }) => makeRpcCall(request), - ); - - expect(result).toStrictEqual(mockResult); - }); - }); - - it(`does not reuse the result of a previous request if it was "${emptyValue}"`, async () => { - const requests = [{ method }, { method }]; - const mockResults = [emptyValue, 'some result']; - - await withMockedCommunications({ providerType }, 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.mockRpcCall({ - request: requests[0], - response: { result: mockResults[0] }, - }); - comms.mockRpcCall({ - request: requests[1], - response: { result: mockResults[1] }, - }); - - const results = await withNetworkClient( - { providerType }, - ({ makeRpcCallsInSeries }) => makeRpcCallsInSeries(requests), - ); - - expect(results).toStrictEqual(mockResults); - }); - }); - } - - it('queues requests while a previous identical call is still pending, then runs the queue when it finishes, reusing the result from the first request', async () => { - const requests = [{ method }, { method }, { method }]; - const mockResults = ['first result', 'second result', 'third result']; - - await withMockedCommunications({ providerType }, 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.mockRpcCall({ - request: requests[0], - response: { result: mockResults[0] }, - delay: 100, - }); - - comms.mockRpcCall({ - request: requests[1], - response: { result: mockResults[1] }, - }); - - comms.mockRpcCall({ - request: requests[2], - response: { result: mockResults[2] }, - }); - - const results = await withNetworkClient( - { providerType }, - async (client) => { - const resultPromises = [ - client.makeRpcCall(requests[0]), - client.makeRpcCall(requests[1]), - client.makeRpcCall(requests[2]), - ]; - const firstResult = await resultPromises[0]; - // The inflight cache middleware uses setTimeout to run the handlers, - // so run them now - client.clock.runAll(); - const remainingResults = await Promise.all(resultPromises.slice(1)); - return [firstResult, ...remainingResults]; - }, - ); - - expect(results).toStrictEqual([ - mockResults[0], - mockResults[0], - mockResults[0], - ]); - }); - }); - - it('throws a custom error if the request to the RPC endpoint returns a 405 response', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { method }; - - // 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.mockRpcCall({ - request, - response: { - httpStatus: 405, - }, - }); - const promiseForResult = withNetworkClient( - { providerType }, - async ({ makeRpcCall }) => makeRpcCall(request), - ); - - await expect(promiseForResult).rejects.toThrow( - 'The method does not exist / is not available', - ); - }); - }); - - // There is a difference in how we are testing the Infura middleware vs. the - // custom RPC middleware (or, more specifically, the fetch middleware) because - // of what both middleware treat as rate limiting errors. In this case, the - // fetch middleware treats a 418 response from the RPC endpoint as such an - // error, whereas to the Infura middleware, it is a 429 response. - if (providerType === 'infura') { - it('throws an undescriptive error if the request to the RPC endpoint returns a 418 response', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { id: 123, method }; - - // 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.mockRpcCall({ - request, - response: { - httpStatus: 418, - }, - }); - const promiseForResult = withNetworkClient( - { providerType }, - async ({ makeRpcCall }) => makeRpcCall(request), - ); - - await expect(promiseForResult).rejects.toThrow( - '{"id":123,"jsonrpc":"2.0"}', - ); - }); - }); - - it('throws an error with a custom message if the request to the RPC endpoint returns a 429 response', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { method }; - - // 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.mockRpcCall({ - request, - response: { - httpStatus: 429, - }, - }); - const promiseForResult = withNetworkClient( - { providerType }, - async ({ makeRpcCall }) => makeRpcCall(request), - ); - - await expect(promiseForResult).rejects.toThrow( - 'Request is being rate limited', - ); - }); - }); - } else { - it('throws a custom error if the request to the RPC endpoint returns a 418 response', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { method }; - - // 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.mockRpcCall({ - request, - response: { - httpStatus: 418, - }, - }); - const promiseForResult = withNetworkClient( - { providerType }, - async ({ makeRpcCall }) => makeRpcCall(request), - ); - - await expect(promiseForResult).rejects.toThrow( - 'Request is being rate limited.', - ); - }); - }); - - it('throws an undescriptive error if the request to the RPC endpoint returns a 429 response', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { method }; - - // 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.mockRpcCall({ - request, - response: { - httpStatus: 429, - }, - }); - const promiseForResult = withNetworkClient( - { providerType }, - async ({ makeRpcCall }) => makeRpcCall(request), - ); - - await expect(promiseForResult).rejects.toThrow( - "Non-200 status code: '429'", - ); - }); - }); - } - - it('throws a generic, undescriptive error if the request to the RPC endpoint returns a response that is not 405, 418, 429, 503, or 504', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { method }; - - // 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.mockRpcCall({ - request, - response: { - id: 12345, - jsonrpc: '2.0', - error: 'some error', - httpStatus: 420, - }, - }); - const promiseForResult = withNetworkClient( - { providerType }, - async ({ makeRpcCall }) => makeRpcCall(request), - ); - - const errorMessage = - providerType === 'infura' - ? '{"id":12345,"jsonrpc":"2.0","error":"some error"}' - : "Non-200 status code: '420'"; - await expect(promiseForResult).rejects.toThrow(errorMessage); - }); - }); - - [503, 504].forEach((httpStatus) => { - it(`retries the request to the RPC endpoint up to 5 times if it returns a ${httpStatus} response, returning the successful result if there is one on the 5th try`, async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { method }; - - // 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(); - // Here we have the request fail for the first 4 tries, then succeed - // on the 5th try. - comms.mockRpcCall({ - request, - response: { - error: 'Some error', - httpStatus, - }, - times: 4, - }); - comms.mockRpcCall({ - request, - response: { - result: 'the result', - httpStatus: 200, - }, - }); - const result = await withNetworkClient( - { providerType }, - async ({ makeRpcCall, clock }) => { - return await waitForPromiseToBeFulfilledAfterRunningAllTimers( - makeRpcCall(request), - clock, - ); - }, - ); - - expect(result).toStrictEqual('the result'); - }); - }); - - it(`causes a request to fail with a custom error if the request to the RPC endpoint returns a ${httpStatus} response 5 times in a row`, async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { method }; - - // 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.mockRpcCall({ - request, - response: { - error: 'Some error', - httpStatus, - }, - times: 5, - }); - comms.mockNextBlockTrackerRequest(); - const promiseForResult = withNetworkClient( - { providerType }, - async ({ makeRpcCall, clock }) => { - return await waitForPromiseToBeFulfilledAfterRunningAllTimers( - makeRpcCall(request), - clock, - ); - }, - ); - const err = - providerType === 'infura' - ? buildInfuraClientRetriesExhaustedErrorMessage('Gateway timeout') - : buildJsonRpcEngineEmptyResponseErrorMessage(method); - await expect(promiseForResult).rejects.toThrow(err); - }); - }); - }); - - it('retries the request to the RPC endpoint up to 5 times if an "ETIMEDOUT" error is thrown while making the request, returning the successful result if there is one on the 5th try', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { method }; - - // 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(); - // Here we have the request fail for the first 4 tries, then succeed - // on the 5th try. - comms.mockRpcCall({ - request, - error: 'ETIMEDOUT: Some message', - times: 4, - }); - comms.mockRpcCall({ - request, - response: { - result: 'the result', - httpStatus: 200, - }, - }); - - const result = await withNetworkClient( - { providerType }, - async ({ makeRpcCall, clock }) => { - return await waitForPromiseToBeFulfilledAfterRunningAllTimers( - makeRpcCall(request), - clock, - ); - }, - ); - - expect(result).toStrictEqual('the result'); - }); - }); - - // Both the Infura and fetch middleware detect ETIMEDOUT errors and will - // automatically retry the request to the RPC endpoint in question, but both - // produce a different error if the number of retries is exhausted. - if (providerType === 'infura') { - it('causes a request to fail with a custom error if an "ETIMEDOUT" error is thrown while making the request to the RPC endpoint 5 times in a row', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { method }; - const errorMessage = 'ETIMEDOUT: Some message'; - - // 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.mockRpcCall({ - request, - error: errorMessage, - times: 5, - }); - const promiseForResult = withNetworkClient( - { providerType }, - async ({ makeRpcCall, clock }) => { - return await waitForPromiseToBeFulfilledAfterRunningAllTimers( - makeRpcCall(request), - clock, - ); - }, - ); - - await expect(promiseForResult).rejects.toThrow( - buildInfuraClientRetriesExhaustedErrorMessage(errorMessage), - ); - }); - }); - } else { - it('returns an empty response if an "ETIMEDOUT" error is thrown while making the request to the RPC endpoint 5 times in a row', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { method }; - const errorMessage = 'ETIMEDOUT: Some message'; - - // 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.mockRpcCall({ - request, - error: errorMessage, - times: 5, - }); - const promiseForResult = withNetworkClient( - { providerType }, - async ({ makeRpcCall, clock }) => { - return await waitForPromiseToBeFulfilledAfterRunningAllTimers( - makeRpcCall(request), - clock, - ); - }, - ); - - await expect(promiseForResult).rejects.toThrow( - buildJsonRpcEngineEmptyResponseErrorMessage(method), - ); - }); - }); - } - - // The Infura middleware treats a response that contains an ECONNRESET message - // as an innocuous error that is likely to disappear on a retry. The custom - // RPC middleware, on the other hand, does not specially handle this error. - if (providerType === 'infura') { - it('retries the request to the RPC endpoint up to 5 times if an "ECONNRESET" error is thrown while making the request, returning the successful result if there is one on the 5th try', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { method }; - - // 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(); - // Here we have the request fail for the first 4 tries, then succeed - // on the 5th try. - comms.mockRpcCall({ - request, - error: 'ECONNRESET: Some message', - times: 4, - }); - comms.mockRpcCall({ - request, - response: { - result: 'the result', - httpStatus: 200, - }, - }); - - const result = await withNetworkClient( - { providerType }, - async ({ makeRpcCall, clock }) => { - return await waitForPromiseToBeFulfilledAfterRunningAllTimers( - makeRpcCall(request), - clock, - ); - }, - ); - - expect(result).toStrictEqual('the result'); - }); - }); - - it('causes a request to fail with a custom error if an "ECONNRESET" error is thrown while making the request to the RPC endpoint 5 times in a row', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { method }; - const errorMessage = 'ECONNRESET: Some message'; - - // 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.mockRpcCall({ - request, - error: errorMessage, - times: 5, - }); - const promiseForResult = withNetworkClient( - { providerType }, - async ({ makeRpcCall, clock }) => { - return await waitForPromiseToBeFulfilledAfterRunningAllTimers( - makeRpcCall(request), - clock, - ); - }, - ); - - await expect(promiseForResult).rejects.toThrow( - buildInfuraClientRetriesExhaustedErrorMessage(errorMessage), - ); - }); - }); - } else { - it('does not retry the request to the RPC endpoint, but throws immediately, if an "ECONNRESET" error is thrown while making the request', async () => { - const customRpcUrl = 'http://example.com'; - - await withMockedCommunications( - { providerType, customRpcUrl }, - async (comms) => { - const request = { method }; - const errorMessage = 'ECONNRESET: Some message'; - - // 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.mockRpcCall({ - request, - error: errorMessage, - }); - const promiseForResult = withNetworkClient( - { providerType, customRpcUrl }, - async ({ makeRpcCall }) => makeRpcCall(request), - ); - - await expect(promiseForResult).rejects.toThrow( - buildFetchFailedErrorMessage(customRpcUrl, errorMessage), - ); - }, - ); - }); - } - - // Both the Infura and fetch middleware will attempt to parse the response - // body as JSON, and if this step produces an error, both middleware will also - // attempt to retry the request. However, this error handling code is slightly - // different between the two. As the error in this case is a SyntaxError, the - // Infura middleware will catch it immediately, whereas the custom RPC - // middleware will catch it and re-throw a separate error, which it then - // catches later. - if (providerType === 'infura') { - it('retries the request to the RPC endpoint up to 5 times if an "SyntaxError" error is thrown while making the request, returning the successful result if there is one on the 5th try', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { method }; - - // 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(); - // Here we have the request fail for the first 4 tries, then succeed - // on the 5th try. - comms.mockRpcCall({ - request, - error: 'SyntaxError: Some message', - times: 4, - }); - comms.mockRpcCall({ - request, - response: { - result: 'the result', - httpStatus: 200, - }, - }); - - const result = await withNetworkClient( - { providerType }, - async ({ makeRpcCall, clock }) => { - return await waitForPromiseToBeFulfilledAfterRunningAllTimers( - makeRpcCall(request), - clock, - ); - }, - ); - - expect(result).toStrictEqual('the result'); - }); - }); - - it('causes a request to fail with a custom error if an "SyntaxError" error is thrown while making the request to the RPC endpoint 5 times in a row', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { method }; - const errorMessage = 'SyntaxError: Some message'; - - // 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.mockRpcCall({ - request, - error: errorMessage, - times: 5, - }); - const promiseForResult = withNetworkClient( - { providerType }, - async ({ makeRpcCall, clock }) => { - return await waitForPromiseToBeFulfilledAfterRunningAllTimers( - makeRpcCall(request), - clock, - ); - }, - ); - - await expect(promiseForResult).rejects.toThrow( - buildInfuraClientRetriesExhaustedErrorMessage(errorMessage), - ); - }); - }); - - it('does not retry the request to the RPC endpoint, but throws immediately, if a "failed to parse response body" error is thrown while making the request', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { method }; - const errorMessage = 'failed to parse response body: some message'; - - // 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.mockRpcCall({ - request, - error: errorMessage, - }); - const promiseForResult = withNetworkClient( - { providerType, infuraNetwork: comms.infuraNetwork }, - async ({ makeRpcCall }) => makeRpcCall(request), - ); - - await expect(promiseForResult).rejects.toThrow( - buildFetchFailedErrorMessage(comms.rpcUrl, errorMessage), - ); - }); - }); - } else { - it('does not retry the request to the RPC endpoint, but throws immediately, if a "SyntaxError" error is thrown while making the request', async () => { - const customRpcUrl = 'http://example.com'; - - await withMockedCommunications( - { providerType, customRpcUrl }, - async (comms) => { - const request = { method }; - const errorMessage = 'SyntaxError: Some message'; - - // 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.mockRpcCall({ - request, - error: errorMessage, - }); - const promiseForResult = withNetworkClient( - { providerType, customRpcUrl }, - async ({ makeRpcCall }) => makeRpcCall(request), - ); - - await expect(promiseForResult).rejects.toThrow( - buildFetchFailedErrorMessage(customRpcUrl, errorMessage), - ); - }, - ); - }); - - it('retries the request to the RPC endpoint up to 5 times if a "failed to parse response body" error is thrown while making the request, returning the successful result if there is one on the 5th try', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { method }; - - // 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(); - // Here we have the request fail for the first 4 tries, then succeed - // on the 5th try. - comms.mockRpcCall({ - request, - error: 'failed to parse response body: some message', - times: 4, - }); - comms.mockRpcCall({ - request, - response: { - result: 'the result', - httpStatus: 200, - }, - }); - - const result = await withNetworkClient( - { providerType }, - async ({ makeRpcCall, clock }) => { - return await waitForPromiseToBeFulfilledAfterRunningAllTimers( - makeRpcCall(request), - clock, - ); - }, - ); - - expect(result).toStrictEqual('the result'); - }); - }); - - it('returns an empty response if a "failed to parse response body" error is thrown while making the request to the RPC endpoint 5 times in a row', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { method }; - const errorMessage = 'failed to parse response body: some message'; - - // 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.mockRpcCall({ - request, - error: errorMessage, - times: 5, - }); - const promiseForResult = withNetworkClient( - { providerType }, - async ({ makeRpcCall, clock }) => { - return await waitForPromiseToBeFulfilledAfterRunningAllTimers( - makeRpcCall(request), - clock, - ); - }, - ); - - await expect(promiseForResult).rejects.toThrow( - buildJsonRpcEngineEmptyResponseErrorMessage(method), - ); - }); - }); - } - - // Only the custom RPC middleware will detect a "Failed to fetch" error and - // attempt to retry the request to the RPC endpoint; the Infura middleware - // does not. - if (providerType === 'infura') { - it('does not retry the request to the RPC endpoint, but throws immediately, if a "Failed to fetch" error is thrown while making the request', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { method }; - const errorMessage = 'Failed to fetch: some message'; - - // 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.mockRpcCall({ - request, - error: errorMessage, - }); - const promiseForResult = withNetworkClient( - { providerType, infuraNetwork: comms.infuraNetwork }, - async ({ makeRpcCall }) => makeRpcCall(request), - ); - - await expect(promiseForResult).rejects.toThrow( - buildFetchFailedErrorMessage(comms.rpcUrl, errorMessage), - ); - }); - }); - } else { - it('retries the request to the RPC endpoint up to 5 times if a "Failed to fetch" error is thrown while making the request, returning the successful result if there is one on the 5th try', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { method }; - - // 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(); - // Here we have the request fail for the first 4 tries, then succeed - // on the 5th try. - comms.mockRpcCall({ - request, - error: 'Failed to fetch: some message', - times: 4, - }); - comms.mockRpcCall({ - request, - response: { - result: 'the result', - httpStatus: 200, - }, - }); - - const result = await withNetworkClient( - { providerType }, - async ({ makeRpcCall, clock }) => { - return await waitForPromiseToBeFulfilledAfterRunningAllTimers( - makeRpcCall(request), - clock, - ); - }, - ); - - expect(result).toStrictEqual('the result'); - }); - }); - - it('returns an empty response if a "Failed to fetch" error is thrown while making the request to the RPC endpoint 5 times in a row', async () => { - await withMockedCommunications({ providerType }, async (comms) => { - const request = { method }; - const errorMessage = 'Failed to fetch: some message'; - - // 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.mockRpcCall({ - request, - error: errorMessage, - times: 5, - }); - const promiseForResult = withNetworkClient( - { providerType }, - async ({ makeRpcCall, clock }) => { - return await waitForPromiseToBeFulfilledAfterRunningAllTimers( - makeRpcCall(request), - clock, - ); - }, - ); - - await expect(promiseForResult).rejects.toThrow( - buildJsonRpcEngineEmptyResponseErrorMessage(method), - ); - }); - }); - } -} diff --git a/app/scripts/controllers/network/provider-api-tests/not-handled-by-middleware.ts b/app/scripts/controllers/network/provider-api-tests/not-handled-by-middleware.ts deleted file mode 100644 index ee92bb07f..000000000 --- a/app/scripts/controllers/network/provider-api-tests/not-handled-by-middleware.ts +++ /dev/null @@ -1,63 +0,0 @@ -/* eslint-disable jest/require-top-level-describe, jest/no-export */ - -import { fill } from 'lodash'; -import { - ProviderType, - withMockedCommunications, - withNetworkClient, -} from './helpers'; - -type TestsForRpcMethodNotHandledByMiddlewareOptions = { - providerType: ProviderType; - numberOfParameters: number; -}; - -/** - * Defines tests which exercise the behavior exhibited by an RPC method that - * is not handled specially by the network client middleware. - * - * @param method - The name of the RPC method under test. - * @param additionalArgs - Additional arguments. - * @param additionalArgs.providerType - The type of provider being tested; - * either `infura` or `custom`. - * @param additionalArgs.numberOfParameters - The number of parameters that this - * RPC method takes. - */ -export function testsForRpcMethodNotHandledByMiddleware( - method: string, - { - providerType, - numberOfParameters, - }: TestsForRpcMethodNotHandledByMiddlewareOptions, -) { - if (providerType !== 'infura' && providerType !== 'custom') { - throw new Error( - `providerType must be either "infura" or "custom", was "${providerType}" instead`, - ); - } - - it('attempts to pass the request off to the RPC endpoint', async () => { - const request = { - method, - params: fill(Array(numberOfParameters), 'some value'), - }; - const expectedResult = 'the result'; - - await withMockedCommunications({ providerType }, 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.mockRpcCall({ - request, - response: { result: expectedResult }, - }); - const actualResult = await withNetworkClient( - { providerType }, - ({ makeRpcCall }) => makeRpcCall(request), - ); - - expect(actualResult).toStrictEqual(expectedResult); - }); - }); -} diff --git a/app/scripts/controllers/network/provider-api-tests/shared-tests.ts b/app/scripts/controllers/network/provider-api-tests/shared-tests.ts deleted file mode 100644 index cf34302ce..000000000 --- a/app/scripts/controllers/network/provider-api-tests/shared-tests.ts +++ /dev/null @@ -1,372 +0,0 @@ -/* eslint-disable jest/require-top-level-describe, jest/no-export, jest/no-identical-title */ - -import { testsForRpcMethodsThatCheckForBlockHashInResponse } from './block-hash-in-response'; -import { testsForRpcMethodSupportingBlockParam } from './block-param'; -import { - ProviderType, - withMockedCommunications, - withNetworkClient, -} from './helpers'; -import { testsForRpcMethodAssumingNoBlockParam } from './no-block-param'; -import { testsForRpcMethodNotHandledByMiddleware } from './not-handled-by-middleware'; - -/** - * Constructs an error message that the Infura client would produce in the event - * that it has attempted to retry the request to Infura and has failed. - * - * @param reason - The exact reason for failure. - * @returns The error message. - */ -export function buildInfuraClientRetriesExhaustedErrorMessage(reason: string) { - return new RegExp( - `^InfuraProvider - cannot complete request. All retries exhausted\\..+${reason}`, - 'us', - ); -} - -/** - * Constructs an error message that JsonRpcEngine would produce in the event - * that the response object is empty as it leaves the middleware. - * - * @param method - The RPC method. - * @returns The error message. - */ -export function buildJsonRpcEngineEmptyResponseErrorMessage(method: string) { - return new RegExp( - `^JsonRpcEngine: Response has no error or result for request:.+"method": "${method}"`, - 'us', - ); -} - -/** - * Constructs an error message that `fetch` with throw if it cannot make a - * request. - * - * @param url - The URL being fetched - * @param reason - The reason. - * @returns The error message. - */ -export function buildFetchFailedErrorMessage(url: string, reason: string) { - return new RegExp( - `^request to ${url}(/[^/ ]*)+ failed, reason: ${reason}`, - 'us', - ); -} - -/** - * Defines tests that are common to both the Infura and JSON-RPC network client. - * - * @param providerType - The type of provider being tested, which determines - * which suite of middleware is being tested. If `infura`, then the middleware - * exposed by `createInfuraClient` is tested; if `custom`, then the middleware - * exposed by `createJsonRpcClient` will be tested. - */ -export function testsForProviderType(providerType: ProviderType) { - // Ethereum JSON-RPC spec: - // Infura documentation: - - describe('methods included in the Ethereum JSON-RPC spec', () => { - describe('methods not handled by middleware', () => { - const notHandledByMiddleware = [ - // This list is presented in the same order as in the network client - // tests on the core side. - - { name: 'eth_newFilter', numberOfParameters: 1 }, - { name: 'eth_getFilterChanges', numberOfParameters: 1 }, - { name: 'eth_newBlockFilter', numberOfParameters: 0 }, - { name: 'eth_newPendingTransactionFilter', numberOfParameters: 0 }, - { name: 'eth_uninstallFilter', numberOfParameters: 1 }, - - { name: 'eth_sendRawTransaction', numberOfParameters: 1 }, - { name: 'eth_sendTransaction', numberOfParameters: 1 }, - { name: 'eth_sign', numberOfParameters: 2 }, - - { name: 'eth_createAccessList', numberOfParameters: 2 }, - { name: 'eth_getLogs', numberOfParameters: 1 }, - { name: 'eth_getProof', numberOfParameters: 3 }, - { name: 'eth_getWork', numberOfParameters: 0 }, - { name: 'eth_maxPriorityFeePerGas', numberOfParameters: 0 }, - { name: 'eth_submitHashRate', numberOfParameters: 2 }, - { name: 'eth_submitWork', numberOfParameters: 3 }, - { name: 'eth_syncing', numberOfParameters: 0 }, - { name: 'eth_feeHistory', numberOfParameters: 3 }, - { name: 'debug_getRawHeader', numberOfParameters: 1 }, - { name: 'debug_getRawBlock', numberOfParameters: 1 }, - { name: 'debug_getRawTransaction', numberOfParameters: 1 }, - { name: 'debug_getRawReceipts', numberOfParameters: 1 }, - { name: 'debug_getBadBlocks', numberOfParameters: 0 }, - - { name: 'eth_accounts', numberOfParameters: 0 }, - { name: 'eth_coinbase', numberOfParameters: 0 }, - { name: 'eth_hashrate', numberOfParameters: 0 }, - { name: 'eth_mining', numberOfParameters: 0 }, - - { name: 'eth_signTransaction', numberOfParameters: 1 }, - ]; - notHandledByMiddleware.forEach(({ name, numberOfParameters }) => { - describe(`method name: ${name}`, () => { - testsForRpcMethodNotHandledByMiddleware(name, { - providerType, - numberOfParameters, - }); - }); - }); - }); - - describe('methods with block hashes in their result', () => { - const methodsWithBlockHashInResponse = [ - { name: 'eth_getTransactionByHash', numberOfParameters: 1 }, - { name: 'eth_getTransactionReceipt', numberOfParameters: 1 }, - ]; - methodsWithBlockHashInResponse.forEach(({ name, numberOfParameters }) => { - describe(`method name: ${name}`, () => { - testsForRpcMethodsThatCheckForBlockHashInResponse(name, { - numberOfParameters, - providerType, - }); - }); - }); - }); - - describe('methods that assume there is no block param', () => { - const assumingNoBlockParam = [ - { name: 'eth_getFilterLogs', numberOfParameters: 1 }, - { name: 'eth_blockNumber', numberOfParameters: 0 }, - { name: 'eth_estimateGas', numberOfParameters: 2 }, - { name: 'eth_gasPrice', numberOfParameters: 0 }, - { name: 'eth_getBlockByHash', numberOfParameters: 2 }, - { - name: 'eth_getBlockTransactionCountByHash', - numberOfParameters: 1, - }, - { - name: 'eth_getTransactionByBlockHashAndIndex', - numberOfParameters: 2, - }, - { name: 'eth_getUncleByBlockHashAndIndex', numberOfParameters: 2 }, - { name: 'eth_getUncleCountByBlockHash', numberOfParameters: 1 }, - ]; - const blockParamIgnored = [ - { name: 'eth_getUncleCountByBlockNumber', numberOfParameters: 1 }, - { name: 'eth_getUncleByBlockNumberAndIndex', numberOfParameters: 2 }, - { - name: 'eth_getTransactionByBlockNumberAndIndex', - numberOfParameters: 2, - }, - { - name: 'eth_getBlockTransactionCountByNumber', - numberOfParameters: 1, - }, - ]; - assumingNoBlockParam - .concat(blockParamIgnored) - .forEach(({ name, numberOfParameters }) => - describe(`method name: ${name}`, () => { - testsForRpcMethodAssumingNoBlockParam(name, { - providerType, - numberOfParameters, - }); - }), - ); - }); - - describe('methods that have a param to specify the block', () => { - const supportingBlockParam = [ - { - name: 'eth_call', - blockParamIndex: 1, - numberOfParameters: 2, - }, - { - name: 'eth_getBalance', - blockParamIndex: 1, - numberOfParameters: 2, - }, - { - name: 'eth_getBlockByNumber', - blockParamIndex: 0, - numberOfParameters: 2, - }, - { name: 'eth_getCode', blockParamIndex: 1, numberOfParameters: 2 }, - { - name: 'eth_getStorageAt', - blockParamIndex: 2, - numberOfParameters: 3, - }, - { - name: 'eth_getTransactionCount', - blockParamIndex: 1, - numberOfParameters: 2, - }, - ]; - supportingBlockParam.forEach( - ({ name, blockParamIndex, numberOfParameters }) => { - describe(`method name: ${name}`, () => { - testsForRpcMethodSupportingBlockParam(name, { - providerType, - blockParamIndex, - numberOfParameters, - }); - }); - }, - ); - }); - - describe('other methods', () => { - describe('eth_getTransactionByHash', () => { - it("refreshes the block tracker's current block if it is less than the block number that comes back in the response", async () => { - const method = 'eth_getTransactionByHash'; - - await withMockedCommunications({ providerType }, async (comms) => { - const request = { method }; - - comms.mockNextBlockTrackerRequest({ blockNumber: '0x100' }); - // This is our request. - comms.mockRpcCall({ - request, - response: { - result: { - blockNumber: '0x200', - }, - }, - }); - comms.mockNextBlockTrackerRequest({ blockNumber: '0x300' }); - - await withNetworkClient( - { providerType }, - async ({ makeRpcCall, blockTracker }) => { - await makeRpcCall(request); - expect(blockTracker.getCurrentBlock()).toStrictEqual('0x300'); - }, - ); - }); - }); - }); - - describe('eth_getTransactionReceipt', () => { - it("refreshes the block tracker's current block if it is less than the block number that comes back in the response", async () => { - const method = 'eth_getTransactionReceipt'; - - await withMockedCommunications({ providerType }, async (comms) => { - const request = { method }; - - comms.mockNextBlockTrackerRequest({ blockNumber: '0x100' }); - // This is our request. - comms.mockRpcCall({ - request, - response: { - result: { - blockNumber: '0x200', - }, - }, - }); - comms.mockNextBlockTrackerRequest({ blockNumber: '0x300' }); - - await withNetworkClient( - { providerType }, - async ({ makeRpcCall, blockTracker }) => { - await makeRpcCall(request); - expect(blockTracker.getCurrentBlock()).toStrictEqual('0x300'); - }, - ); - }); - }); - }); - - describe('eth_chainId', () => { - it('does not hit the RPC endpoint, instead returning the configured chain id', async () => { - const networkId = await withNetworkClient( - { providerType: 'custom', customChainId: '0x1' }, - ({ makeRpcCall }) => { - return makeRpcCall({ method: 'eth_chainId' }); - }, - ); - - expect(networkId).toStrictEqual('0x1'); - }); - }); - }); - }); - - describe('methods not included in the Ethereum JSON-RPC spec', () => { - describe('methods not handled by middleware', () => { - const notHandledByMiddleware = [ - // This list is presented in the same order as in the network client - // tests on the core side. - - { name: 'net_listening', numberOfParameters: 0 }, - { name: 'eth_subscribe', numberOfParameters: 1 }, - { name: 'eth_unsubscribe', numberOfParameters: 1 }, - { name: 'custom_rpc_method', numberOfParameters: 1 }, - { name: 'net_peerCount', numberOfParameters: 0 }, - { name: 'parity_nextNonce', numberOfParameters: 1 }, - ]; - notHandledByMiddleware.forEach(({ name, numberOfParameters }) => { - describe(`method name: ${name}`, () => { - testsForRpcMethodNotHandledByMiddleware(name, { - providerType, - numberOfParameters, - }); - }); - }); - }); - - describe('methods that assume there is no block param', () => { - const assumingNoBlockParam = [ - { name: 'web3_clientVersion', numberOfParameters: 0 }, - { name: 'eth_protocolVersion', numberOfParameters: 0 }, - ]; - assumingNoBlockParam.forEach(({ name, numberOfParameters }) => - describe(`method name: ${name}`, () => { - testsForRpcMethodAssumingNoBlockParam(name, { - providerType, - numberOfParameters, - }); - }), - ); - }); - - describe('other methods', () => { - describe('net_version', () => { - // The Infura middleware includes `net_version` in its scaffold - // middleware, whereas the custom RPC middleware does not. - if (providerType === 'infura') { - it('does not hit Infura, instead returning the network ID that maps to the Infura network, as a decimal string', async () => { - const networkId = await withNetworkClient( - { providerType: 'infura', infuraNetwork: 'goerli' }, - ({ makeRpcCall }) => { - return makeRpcCall({ - method: 'net_version', - }); - }, - ); - expect(networkId).toStrictEqual('5'); - }); - } else { - it('hits the RPC endpoint', async () => { - await withMockedCommunications( - { providerType: 'custom' }, - async (comms) => { - comms.mockRpcCall({ - request: { method: 'net_version' }, - response: { result: '1' }, - }); - - const networkId = await withNetworkClient( - { providerType: 'custom' }, - ({ makeRpcCall }) => { - return makeRpcCall({ - method: 'net_version', - }); - }, - ); - - expect(networkId).toStrictEqual('1'); - }, - ); - }); - } - }); - }); - }); -} diff --git a/app/scripts/controllers/network/test/fake-block-tracker.ts b/app/scripts/controllers/network/test/fake-block-tracker.ts deleted file mode 100644 index 89f8309d1..000000000 --- a/app/scripts/controllers/network/test/fake-block-tracker.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { PollingBlockTracker } from 'eth-block-tracker'; - -/** - * Acts like a PollingBlockTracker, but doesn't start the polling loop or - * make any requests. - */ -export class FakeBlockTracker extends PollingBlockTracker { - async _start() { - // do nothing - } -} diff --git a/app/scripts/controllers/network/test/fake-provider.ts b/app/scripts/controllers/network/test/fake-provider.ts deleted file mode 100644 index a2820112d..000000000 --- a/app/scripts/controllers/network/test/fake-provider.ts +++ /dev/null @@ -1,206 +0,0 @@ -import { inspect, isDeepStrictEqual } from 'util'; -import { - JsonRpcEngine, - JsonRpcRequest, - JsonRpcResponse, -} from 'json-rpc-engine'; -import { SafeEventEmitterProvider } from '@metamask/eth-json-rpc-provider/dist/safe-event-emitter-provider'; - -// Store this in case it gets stubbed later -const originalSetTimeout = global.setTimeout; - -/** - * An object that allows specifying the behavior of a specific invocation of - * `sendAsync`. The `method` always identifies the stub, but the behavior - * may be specified multiple ways: `sendAsync` can either return a promise or - * throw an error, and if it returns a promise, that promise can either be - * resolved with a response object or reject with an error. - * - * @property request - Looks for a request matching these specifications. - * @property request.method - The RPC method to which this stub will be matched. - * @property request.params - The params to which this stub will be matched. - * @property response - Instructs `sendAsync` to return a promise that resolves - * with a response object. - * @property response.result - Specifies a successful response, with this as the - * `result`. - * @property response.error - Specifies an error response, with this as the - * `error`. - * @property error - Instructs `sendAsync` to return a promise that rejects with - * this error. - * @property implementation - Allows overriding `sendAsync` entirely. Useful if - * you want it to throw an error. - * @property delay - The amount of time that will pass after the callback is - * called with the response. - * @property discardAfterMatching - Usually after the stub matches a request, it - * is discarded, but setting this to true prevents that from happening. True by - * default. - * @property beforeCompleting - Sometimes it is useful to do something after the - * request is kicked off but before it ends (or, in terms of a `fetch` promise, - * when the promise is initiated but before it is resolved). You can pass an - * (async) function for this option to do this. - */ -export type FakeProviderStub = { - request: { - method: string; - params?: any[]; - }; - delay?: number; - discardAfterMatching?: boolean; - beforeCompleting?: () => void | Promise; -} & ( - | { - response: { result: any } | { error: string }; - } - | { - error: unknown; - } - | { - implementation: () => void; - } -); - -/** - * The set of options that the FakeProviderEngine constructor takes. - * - * @property stubs - A set of objects that allow specifying the behavior - * of specific invocations of `sendAsync` matching a `method`. - */ -interface FakeProviderEngineOptions { - stubs?: FakeProviderStub[]; -} - -/** - * FakeProviderEngine is an implementation of the provider that - * NetworkController exposes, which is actually an instance of - * Web3ProviderEngine (from the `web3-provider-engine` package). Hence it - * supports the same interface as Web3ProviderEngine, except that fake responses - * for any RPC methods that are accessed can be supplied via an API that is more - * succinct than using Jest's mocking API. - */ -// NOTE: We shouldn't need to extend from the "real" provider here, but -// we'd need a `SafeEventEmitterProvider` _interface_ and that doesn't exist (at -// least not yet). -export class FakeProvider extends SafeEventEmitterProvider { - calledStubs: FakeProviderStub[]; - - #originalStubs: FakeProviderStub[]; - - #stubs: FakeProviderStub[]; - - /** - * Makes a new instance of the fake provider. - * - * @param options - The options. - * @param options.stubs - A set of objects that allow specifying the behavior - * of specific invocations of `sendAsync` matching a `method`. - */ - constructor({ stubs = [] }: FakeProviderEngineOptions) { - super({ engine: new JsonRpcEngine() }); - this.#originalStubs = stubs; - this.#stubs = this.#originalStubs.slice(); - this.calledStubs = []; - } - - send = ( - payload: JsonRpcRequest, - callback: (error: unknown, response?: JsonRpcResponse) => void, - ) => { - return this.#handleSend(payload, callback); - }; - - sendAsync = ( - payload: JsonRpcRequest, - callback: (error: unknown, response?: JsonRpcResponse) => void, - ) => { - return this.#handleSend(payload, callback); - }; - - #handleSend( - payload: JsonRpcRequest, - callback: (error: unknown, response?: JsonRpcResponse) => void, - ) { - if (Array.isArray(payload)) { - throw new Error("Arrays aren't supported"); - } - - const index = this.#stubs.findIndex((stub) => { - return ( - stub.request.method === payload.method && - (!('params' in stub.request) || - isDeepStrictEqual(stub.request.params, payload.params)) - ); - }); - - if (index === -1) { - const matchingCalledStubs = this.calledStubs.filter((stub) => { - return ( - stub.request.method === payload.method && - (!('params' in stub.request) || - isDeepStrictEqual(stub.request.params, payload.params)) - ); - }); - let message = `Could not find any stubs matching: ${inspect(payload, { - depth: null, - })}`; - if (matchingCalledStubs.length > 0) { - message += `\n\nIt appears the following stubs were defined, but have been called already:\n\n${inspect( - matchingCalledStubs, - { depth: null }, - )}`; - } - - throw new Error(message); - } else { - const stub = this.#stubs[index]; - - if (stub.discardAfterMatching !== false) { - this.#stubs.splice(index, 1); - } - - if (stub.delay) { - originalSetTimeout(() => { - this.#handleRequest(stub, callback); - }, stub.delay); - } else { - this.#handleRequest(stub, callback); - } - - this.calledStubs.push({ ...stub }); - } - } - - async #handleRequest( - stub: FakeProviderStub, - callback: (error: unknown, response?: JsonRpcResponse) => void, - ) { - if (stub.beforeCompleting) { - await stub.beforeCompleting(); - } - - if ('implementation' in stub) { - stub.implementation(); - return; - } - - if ('response' in stub) { - if ('result' in stub.response) { - callback(null, { - jsonrpc: '2.0', - id: 1, - result: stub.response.result, - }); - } else if ('error' in stub.response) { - callback(null, { - jsonrpc: '2.0', - id: 1, - error: { - code: -999, - message: stub.response.error, - }, - }); - } - } else if ('error' in stub) { - callback(stub.error); - } - } -} diff --git a/app/scripts/controllers/swaps.js b/app/scripts/controllers/swaps.js index 505b696d7..295428813 100644 --- a/app/scripts/controllers/swaps.js +++ b/app/scripts/controllers/swaps.js @@ -147,9 +147,9 @@ export default class SwapsController { this.indexOfNewestCallInFlight = 0; this.ethersProvider = new Web3Provider(provider); - this._currentNetworkId = networkController.store.getState().networkId; + this._currentNetworkId = networkController.state.networkId; onNetworkStateChange(() => { - const { networkId, networkStatus } = networkController.store.getState(); + const { networkId, networkStatus } = networkController.state; if ( networkStatus === NetworkStatus.Available && networkId !== this._currentNetworkId diff --git a/app/scripts/controllers/swaps.test.js b/app/scripts/controllers/swaps.test.js index e3dbaafda..a17d131ca 100644 --- a/app/scripts/controllers/swaps.test.js +++ b/app/scripts/controllers/swaps.test.js @@ -100,11 +100,9 @@ const MOCK_GET_BUFFERED_GAS_LIMIT = async () => ({ function getMockNetworkController() { return { - store: { - getState: sinon.stub().returns({ - networkId: NETWORK_IDS.GOERLI, - networkStatus: NetworkStatus.Available, - }), + state: { + networkId: NETWORK_IDS.GOERLI, + networkStatus: NetworkStatus.Available, }, }; } @@ -208,7 +206,10 @@ describe('SwapsController', function () { it('should replace ethers instance when network changes', function () { const networkController = getMockNetworkController(); - const onNetworkStateChange = sinon.stub(); + let networkStateChangeListener; + const onNetworkStateChange = (listener) => { + networkStateChangeListener = listener; + }; const swapsController = new SwapsController({ getBufferedGasLimit: MOCK_GET_BUFFERED_GAS_LIMIT, networkController, @@ -220,13 +221,12 @@ describe('SwapsController', function () { getCurrentChainId: getCurrentChainIdStub, }); const currentEthersInstance = swapsController.ethersProvider; - const changeNetwork = onNetworkStateChange.getCall(0).args[0]; - networkController.store.getState.returns({ + networkController.state = { networkId: NETWORK_IDS.MAINNET, networkStatus: NetworkStatus.Available, - }); - changeNetwork(NETWORK_IDS.MAINNET); + }; + networkStateChangeListener(); const newEthersInstance = swapsController.ethersProvider; assert.notStrictEqual( @@ -238,7 +238,10 @@ describe('SwapsController', function () { it('should not replace ethers instance when network changes to loading', function () { const networkController = getMockNetworkController(); - const onNetworkStateChange = sinon.stub(); + let networkStateChangeListener; + const onNetworkStateChange = (listener) => { + networkStateChangeListener = listener; + }; const swapsController = new SwapsController({ getBufferedGasLimit: MOCK_GET_BUFFERED_GAS_LIMIT, networkController, @@ -250,13 +253,12 @@ describe('SwapsController', function () { getCurrentChainId: getCurrentChainIdStub, }); const currentEthersInstance = swapsController.ethersProvider; - const changeNetwork = onNetworkStateChange.getCall(0).args[0]; - networkController.store.getState.returns({ + networkController.state = { networkId: null, networkStatus: NetworkStatus.Unknown, - }); - changeNetwork('loading'); + }; + networkStateChangeListener(); const newEthersInstance = swapsController.ethersProvider; assert.strictEqual( @@ -268,7 +270,10 @@ describe('SwapsController', function () { it('should not replace ethers instance when network changes to the same network', function () { const networkController = getMockNetworkController(); - const onNetworkStateChange = sinon.stub(); + let networkStateChangeListener; + const onNetworkStateChange = (listener) => { + networkStateChangeListener = listener; + }; const swapsController = new SwapsController({ getBufferedGasLimit: MOCK_GET_BUFFERED_GAS_LIMIT, networkController, @@ -280,13 +285,12 @@ describe('SwapsController', function () { getCurrentChainId: getCurrentChainIdStub, }); const currentEthersInstance = swapsController.ethersProvider; - const changeNetwork = onNetworkStateChange.getCall(0).args[0]; - networkController.store.getState.returns({ + networkController.state = { networkId: NETWORK_IDS.GOERLI, networkStatus: NetworkStatus.Available, - }); - changeNetwork(NETWORK_IDS.GOERLI); + }; + networkStateChangeListener(); const newEthersInstance = swapsController.ethersProvider; assert.strictEqual( diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 9613a9aa6..0ccdb519d 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -39,6 +39,7 @@ import { } from '@metamask/assets-controllers'; import { PhishingController } from '@metamask/phishing-controller'; import { AnnouncementController } from '@metamask/announcement-controller'; +import { NetworkController } from '@metamask/network-controller'; import { GasFeeController } from '@metamask/gas-fee-controller'; import { PermissionController, @@ -105,6 +106,7 @@ import { import { CHAIN_IDS, NETWORK_TYPES, + TEST_NETWORK_TICKER_MAP, NetworkStatus, } from '../../shared/constants/network'; import { HardwareDeviceNames } from '../../shared/constants/hardware-wallets'; @@ -167,7 +169,6 @@ import createTabIdMiddleware from './lib/createTabIdMiddleware'; import createOnboardingMiddleware from './lib/createOnboardingMiddleware'; import { setupMultiplex } from './lib/stream-utils'; import EnsController from './controllers/ens'; -import { NetworkController } from './controllers/network'; import PreferencesController from './controllers/preferences'; import AppStateController from './controllers/app-state'; import CachedBalancesController from './controllers/cached-balances'; @@ -302,6 +303,7 @@ export default class MetamaskController extends EventEmitter { const networkControllerMessenger = this.controllerMessenger.getRestricted({ name: 'NetworkController', allowedEvents: [ + 'NetworkController:stateChange', 'NetworkController:networkWillChange', 'NetworkController:networkDidChange', 'NetworkController:infuraIsBlocked', @@ -309,9 +311,34 @@ export default class MetamaskController extends EventEmitter { ], }); + let initialProviderConfig; + if (process.env.IN_TEST) { + initialProviderConfig = { + type: NETWORK_TYPES.RPC, + rpcUrl: 'http://localhost:8545', + chainId: '0x539', + nickname: 'Localhost 8545', + ticker: 'ETH', + }; + } else if ( + process.env.METAMASK_DEBUG || + process.env.METAMASK_ENVIRONMENT === 'test' + ) { + initialProviderConfig = { + type: NETWORK_TYPES.GOERLI, + chainId: CHAIN_IDS.GOERLI, + ticker: TEST_NETWORK_TICKER_MAP[NETWORK_TYPES.GOERLI], + }; + } + const initialNetworkControllerState = initialProviderConfig + ? { + providerConfig: initialProviderConfig, + ...initState.NetworkController, + } + : initState.NetworkController; this.networkController = new NetworkController({ messenger: networkControllerMessenger, - state: initState.NetworkController, + state: initialNetworkControllerState, infuraProjectId: opts.infuraProjectId, trackMetaMetricsEvent: (...args) => this.metaMetricsController.trackEvent(...args), @@ -331,13 +358,10 @@ export default class MetamaskController extends EventEmitter { }); this.tokenListController = new TokenListController({ - chainId: this.networkController.store.getState().providerConfig.chainId, + chainId: this.networkController.state.providerConfig.chainId, preventPollingOnNetworkRestart: initState.TokenListController ? initState.TokenListController.preventPollingOnNetworkRestart : true, - onNetworkStateChange: this.networkController.store.subscribe.bind( - this.networkController.store, - ), messenger: tokenListMessenger, state: initState.TokenListController, }); @@ -365,25 +389,28 @@ export default class MetamaskController extends EventEmitter { await updateCurrentLocale(currentLocale); }); + const tokensControllerMessenger = this.controllerMessenger.getRestricted({ + name: 'TokensController', + allowedActions: ['ApprovalController:addRequest'], + allowedEvents: ['NetworkController:stateChange'], + }); this.tokensController = new TokensController({ - chainId: this.networkController.store.getState().providerConfig.chainId, + messenger: tokensControllerMessenger, + chainId: this.networkController.state.providerConfig.chainId, onPreferencesStateChange: this.preferencesController.store.subscribe.bind( this.preferencesController.store, ), - onNetworkStateChange: this.networkController.store.subscribe.bind( - this.networkController.store, + onNetworkStateChange: networkControllerMessenger.subscribe.bind( + networkControllerMessenger, + 'NetworkController:stateChange', ), config: { provider: this.provider }, state: initState.TokensController, - messenger: this.controllerMessenger.getRestricted({ - name: 'TokensController', - allowedActions: [`${this.approvalController.name}:addRequest`], - }), }); this.assetsContractController = new AssetsContractController( { - chainId: this.networkController.store.getState().providerConfig.chainId, + chainId: this.networkController.state.providerConfig.chainId, onPreferencesStateChange: (listener) => this.preferencesController.store.subscribe(listener), // This handler is misnamed, and is a known issue that will be resolved @@ -398,7 +425,7 @@ export default class MetamaskController extends EventEmitter { networkControllerMessenger.subscribe( 'NetworkController:networkDidChange', () => { - const networkState = this.networkController.store.getState(); + const networkState = this.networkController.state; return cb(networkState); }, ), @@ -416,13 +443,14 @@ export default class MetamaskController extends EventEmitter { this.nftController = new NftController( { messenger: nftControllerMessenger, - chainId: this.networkController.store.getState().providerConfig.chainId, + chainId: this.networkController.state.providerConfig.chainId, onPreferencesStateChange: this.preferencesController.store.subscribe.bind( this.preferencesController.store, ), - onNetworkStateChange: this.networkController.store.subscribe.bind( - this.networkController.store, + onNetworkStateChange: networkControllerMessenger.subscribe.bind( + networkControllerMessenger, + 'NetworkController:stateChange', ), getERC721AssetName: this.assetsContractController.getERC721AssetName.bind( @@ -469,13 +497,14 @@ export default class MetamaskController extends EventEmitter { this.nftController.setApiKey(process.env.OPENSEA_KEY); this.nftDetectionController = new NftDetectionController({ - chainId: this.networkController.store.getState().providerConfig.chainId, + chainId: this.networkController.state.providerConfig.chainId, onNftsStateChange: (listener) => this.nftController.subscribe(listener), onPreferencesStateChange: this.preferencesController.store.subscribe.bind( this.preferencesController.store, ), - onNetworkStateChange: this.networkController.store.subscribe.bind( - this.networkController.store, + onNetworkStateChange: networkControllerMessenger.subscribe.bind( + networkControllerMessenger, + 'NetworkController:stateChange', ), getOpenSeaApiKey: () => this.nftController.openSeaApiKey, getBalancesInSingleCall: @@ -494,12 +523,11 @@ export default class MetamaskController extends EventEmitter { 'NetworkController:networkDidChange', ), getNetworkIdentifier: () => { - const { type, rpcUrl } = - this.networkController.store.getState().providerConfig; + const { type, rpcUrl } = this.networkController.state.providerConfig; return type === NETWORK_TYPES.RPC ? rpcUrl : type; }, getCurrentChainId: () => - this.networkController.store.getState().providerConfig.chainId, + this.networkController.state.providerConfig.chainId, version: this.platform.getVersion(), environment: process.env.METAMASK_ENVIRONMENT, extension: this.extension, @@ -531,7 +559,7 @@ export default class MetamaskController extends EventEmitter { onNetworkStateChange: (eventHandler) => { networkControllerMessenger.subscribe( 'NetworkController:networkDidChange', - () => eventHandler(this.networkController.store.getState()), + () => eventHandler(this.networkController.state), ); }, getCurrentNetworkEIP1559Compatibility: @@ -543,12 +571,10 @@ export default class MetamaskController extends EventEmitter { legacyAPIEndpoint: `${gasApiBaseUrl}/networks//gasPrices`, EIP1559APIEndpoint: `${gasApiBaseUrl}/networks//suggestedGasFees`, getCurrentNetworkLegacyGasAPICompatibility: () => { - const { chainId } = - this.networkController.store.getState().providerConfig; + const { chainId } = this.networkController.state.providerConfig; return process.env.IN_TEST || chainId === CHAIN_IDS.MAINNET; }, - getChainId: () => - this.networkController.store.getState().providerConfig.chainId, + getChainId: () => this.networkController.state.providerConfig.chainId, }); this.qrHardwareKeyring = new QRHardwareKeyring(); @@ -577,8 +603,7 @@ export default class MetamaskController extends EventEmitter { messenger: currencyRateMessenger, state: { ...initState.CurrencyController, - nativeCurrency: - this.networkController.store.getState().providerConfig.ticker, + nativeCurrency: this.networkController.state.providerConfig.ticker, }, }); @@ -606,7 +631,7 @@ export default class MetamaskController extends EventEmitter { // token exchange rate tracker this.tokenRatesController = new TokenRatesController( { - chainId: this.networkController.store.getState().providerConfig.chainId, + chainId: this.networkController.state.providerConfig.chainId, onTokensStateChange: (listener) => this.tokensController.subscribe(listener), onCurrencyRateStateChange: (listener) => @@ -614,8 +639,9 @@ export default class MetamaskController extends EventEmitter { `${this.currencyRateController.name}:stateChange`, listener, ), - onNetworkStateChange: this.networkController.store.subscribe.bind( - this.networkController.store, + onNetworkStateChange: networkControllerMessenger.subscribe.bind( + networkControllerMessenger, + 'NetworkController:stateChange', ), }, { @@ -645,7 +671,7 @@ export default class MetamaskController extends EventEmitter { this.ensController = new EnsController({ provider: this.provider, getCurrentChainId: () => - this.networkController.store.getState().providerConfig.chainId, + this.networkController.state.providerConfig.chainId, onNetworkDidChange: networkControllerMessenger.subscribe.bind( networkControllerMessenger, 'NetworkController:networkDidChange', @@ -663,7 +689,7 @@ export default class MetamaskController extends EventEmitter { 'NetworkController:networkDidChange', ), getCurrentChainId: () => - this.networkController.store.getState().providerConfig.chainId, + this.networkController.state.providerConfig.chainId, preferencesController: this.preferencesController, onboardingController: this.onboardingController, initState: initState.IncomingTransactionsController, @@ -674,10 +700,9 @@ export default class MetamaskController extends EventEmitter { provider: this.provider, blockTracker: this.blockTracker, getCurrentChainId: () => - this.networkController.store.getState().providerConfig.chainId, + this.networkController.state.providerConfig.chainId, getNetworkIdentifier: () => { - const { type, rpcUrl } = - this.networkController.store.getState().providerConfig; + const { type, rpcUrl } = this.networkController.state.providerConfig; return type === NETWORK_TYPES.RPC ? rpcUrl : type; }, preferencesController: this.preferencesController, @@ -714,7 +739,7 @@ export default class MetamaskController extends EventEmitter { this.cachedBalancesController = new CachedBalancesController({ accountTracker: this.accountTracker, getCurrentChainId: () => - this.networkController.store.getState().providerConfig.chainId, + this.networkController.state.providerConfig.chainId, initState: initState.CachedBalancesController, }); @@ -988,7 +1013,13 @@ export default class MetamaskController extends EventEmitter { }); ///: END:ONLY_INCLUDE_IN + const detectTokensControllerMessenger = + this.controllerMessenger.getRestricted({ + name: 'DetectTokensController', + allowedEvents: ['NetworkController:stateChange'], + }); this.detectTokensController = new DetectTokensController({ + messenger: detectTokensControllerMessenger, preferences: this.preferencesController, tokensController: this.tokensController, assetsContractController: this.assetsContractController, @@ -1039,29 +1070,24 @@ export default class MetamaskController extends EventEmitter { initState: initState.TransactionController || initState.TransactionManager, getPermittedAccounts: this.getPermittedAccounts.bind(this), - getProviderConfig: () => - this.networkController.store.getState().providerConfig, + getProviderConfig: () => this.networkController.state.providerConfig, getCurrentNetworkEIP1559Compatibility: this.networkController.getEIP1559Compatibility.bind( this.networkController, ), getCurrentAccountEIP1559Compatibility: this.getCurrentAccountEIP1559Compatibility.bind(this), - getNetworkId: () => this.networkController.store.getState().networkId, - getNetworkStatus: () => - this.networkController.store.getState().networkStatus, + getNetworkId: () => this.networkController.state.networkId, + getNetworkStatus: () => this.networkController.state.networkStatus, onNetworkStateChange: (listener) => { - let previousNetworkId = - this.networkController.store.getState().networkId; - this.networkController.store.subscribe((state) => { - if (previousNetworkId !== state.networkId) { - listener(); - previousNetworkId = state.networkId; - } - }); + networkControllerMessenger.subscribe( + 'NetworkController:stateChange', + () => listener(), + ({ networkId }) => networkId, + ); }, getCurrentChainId: () => - this.networkController.store.getState().providerConfig.chainId, + this.networkController.state.providerConfig.chainId, preferencesStore: this.preferencesController.store, txHistoryLimit: 60, signTransaction: this.keyringController.signTransaction.bind( @@ -1139,8 +1165,7 @@ export default class MetamaskController extends EventEmitter { const txMeta = this.txController.txStateManager.getTransaction(txId); let rpcPrefs = {}; if (txMeta.chainId) { - const { networkConfigurations } = - this.networkController.store.getState(); + const { networkConfigurations } = this.networkController.state; const matchingNetworkConfig = Object.values( networkConfigurations, ).find( @@ -1221,8 +1246,7 @@ export default class MetamaskController extends EventEmitter { networkControllerMessenger.subscribe( 'NetworkController:networkDidChange', async () => { - const { ticker } = - this.networkController.store.getState().providerConfig; + const { ticker } = this.networkController.state.providerConfig; try { await this.currencyRateController.setNativeCurrency(ticker); } catch (error) { @@ -1277,7 +1301,7 @@ export default class MetamaskController extends EventEmitter { getAllState: this.getState.bind(this), securityProviderRequest: this.securityProviderRequest.bind(this), getCurrentChainId: () => - this.networkController.store.getState().providerConfig.chainId, + this.networkController.state.providerConfig.chainId, }); this.signatureController.hub.on( @@ -1301,14 +1325,15 @@ export default class MetamaskController extends EventEmitter { this.txController.txGasUtil, ), networkController: this.networkController, - onNetworkStateChange: (listener) => - this.networkController.store.subscribe(listener), + onNetworkStateChange: networkControllerMessenger.subscribe.bind( + networkControllerMessenger, + 'NetworkController:stateChange', + ), provider: this.provider, - getProviderConfig: () => - this.networkController.store.getState().providerConfig, + getProviderConfig: () => this.networkController.state.providerConfig, getTokenRatesState: () => this.tokenRatesController.state, getCurrentChainId: () => - this.networkController.store.getState().providerConfig.chainId, + this.networkController.state.providerConfig.chainId, getEIP1559GasFeeEstimates: this.gasFeeController.fetchGasFeeEstimates.bind( this.gasFeeController, @@ -1318,19 +1343,11 @@ export default class MetamaskController extends EventEmitter { ); this.smartTransactionsController = new SmartTransactionsController( { - onNetworkStateChange: (cb) => { - this.networkController.store.subscribe((networkState) => { - const modifiedNetworkState = { - ...networkState, - providerConfig: { - ...networkState.providerConfig, - }, - }; - return cb(modifiedNetworkState); - }); - }, - getNetwork: () => - this.networkController.store.getState().networkId ?? 'loading', + onNetworkStateChange: networkControllerMessenger.subscribe.bind( + networkControllerMessenger, + 'NetworkController:stateChange', + ), + getNetwork: () => this.networkController.state.networkId ?? 'loading', getNonceLock: this.txController.nonceTracker.getNonceLock.bind( this.txController.nonceTracker, ), @@ -1482,7 +1499,7 @@ export default class MetamaskController extends EventEmitter { MetaMetricsController: this.metaMetricsController.store, AddressBookController: this.addressBookController, CurrencyController: this.currencyRateController, - NetworkController: this.networkController.store, + NetworkController: this.networkController, CachedBalancesController: this.cachedBalancesController.store, AlertController: this.alertController.store, OnboardingController: this.onboardingController.store, @@ -1520,7 +1537,7 @@ export default class MetamaskController extends EventEmitter { this.memStore = new ComposableObservableStore({ config: { AppStateController: this.appStateController.store, - NetworkController: this.networkController.store, + NetworkController: this.networkController, CachedBalancesController: this.cachedBalancesController.store, KeyringController: this.keyringController.memStore, PreferencesController: this.preferencesController.store, @@ -1906,7 +1923,7 @@ export default class MetamaskController extends EventEmitter { updatePublicConfigStore(this.getState()); function updatePublicConfigStore(memState) { - const { chainId } = networkController.store.getState().providerConfig; + const { chainId } = networkController.state.providerConfig; if (memState.networkStatus === NetworkStatus.Available) { publicConfigStore.putState(selectPublicState(chainId, memState)); } @@ -1947,7 +1964,7 @@ export default class MetamaskController extends EventEmitter { getProviderNetworkState(memState) { const { networkId } = memState || this.getState(); return { - chainId: this.networkController.store.getState().providerConfig.chainId, + chainId: this.networkController.state.providerConfig.chainId, networkVersion: networkId ?? 'loading', }; } @@ -2975,8 +2992,7 @@ export default class MetamaskController extends EventEmitter { this.appStateController.setTrezorModel(model); } - keyring.network = - this.networkController.store.getState().providerConfig.type; + keyring.network = this.networkController.state.providerConfig.type; return keyring; } @@ -3862,12 +3878,12 @@ export default class MetamaskController extends EventEmitter { ), getCurrentChainId: () => - this.networkController.store.getState().providerConfig.chainId, + this.networkController.state.providerConfig.chainId, getCurrentRpcUrl: () => - this.networkController.store.getState().providerConfig.rpcUrl, + this.networkController.state.providerConfig.rpcUrl, // network configuration-related getNetworkConfigurations: () => - this.networkController.store.getState().networkConfigurations, + this.networkController.state.networkConfigurations, upsertNetworkConfiguration: this.networkController.upsertNetworkConfiguration.bind( this.networkController, @@ -4286,7 +4302,7 @@ export default class MetamaskController extends EventEmitter { * @returns {object} rpcInfo found in the network configurations list */ findNetworkConfigurationBy(rpcInfo) { - const { networkConfigurations } = this.networkController.store.getState(); + const { networkConfigurations } = this.networkController.state; const networkConfiguration = Object.values(networkConfigurations).find( (configuration) => { return Object.keys(rpcInfo).some((key) => { @@ -4502,9 +4518,7 @@ export default class MetamaskController extends EventEmitter { if (transactionSecurityCheckEnabled) { const chainId = Number( - hexToDecimal( - this.networkController.store.getState().providerConfig.chainId, - ), + hexToDecimal(this.networkController.state.providerConfig.chainId), ); try { diff --git a/jest.config.js b/jest.config.js index 6665b9802..d4cc55266 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,9 +1,6 @@ module.exports = { collectCoverageFrom: [ '/app/scripts/constants/error-utils.js', - '/app/scripts/controllers/network/**/*.js', - '/app/scripts/controllers/network/**/*.ts', - '!/app/scripts/controllers/network/**/test/*.ts', '/app/scripts/controllers/permissions/**/*.js', '/app/scripts/controllers/sign.ts', '/app/scripts/controllers/decrypt-message.ts', @@ -40,8 +37,6 @@ module.exports = { '/app/scripts/constants/error-utils.test.js', '/app/scripts/controllers/app-state.test.js', '/app/scripts/controllers/mmi-controller.test.js', - '/app/scripts/controllers/network/**/*.test.js', - '/app/scripts/controllers/network/**/*.test.ts', '/app/scripts/controllers/permissions/**/*.test.js', '/app/scripts/controllers/sign.test.ts', '/app/scripts/controllers/decrypt-message.test.ts', diff --git a/lavamoat/browserify/beta/policy.json b/lavamoat/browserify/beta/policy.json index 929c69e1b..494ce3924 100644 --- a/lavamoat/browserify/beta/policy.json +++ b/lavamoat/browserify/beta/policy.json @@ -905,50 +905,6 @@ "fetch": true } }, - "@metamask/eth-json-rpc-infura": { - "globals": { - "setTimeout": true - }, - "packages": { - "@metamask/eth-json-rpc-infura>@metamask/utils": true, - "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": true, - "eth-rpc-errors": true, - "json-rpc-engine": true, - "node-fetch": true - } - }, - "@metamask/eth-json-rpc-infura>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "browserify>buffer": true, - "nock>debug": true, - "semver": true, - "superstruct": 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>@metamask/utils": true, - "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>pify": true, - "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true, - "@metamask/safe-event-emitter": true, - "browserify>browser-resolve": true, - "eth-rpc-errors": true, - "json-rpc-engine": true, - "lavamoat>json-stable-stringify": true, - "vinyl>clone": true - } - }, "@metamask/eth-json-rpc-middleware": { "globals": { "URL": true, @@ -977,12 +933,6 @@ "superstruct": true } }, - "@metamask/eth-json-rpc-provider": { - "packages": { - "@metamask/safe-event-emitter": true, - "json-rpc-engine": true - } - }, "@metamask/eth-keyring-controller": { "packages": { "@metamask/browser-passworder": true, @@ -1588,6 +1538,100 @@ "browserify>url": true } }, + "@metamask/network-controller": { + "globals": { + "URL": true, + "btoa": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/base-controller": true, + "@metamask/controller-utils": true, + "@metamask/eth-json-rpc-middleware": true, + "@metamask/network-controller>@metamask/eth-json-rpc-infura": true, + "@metamask/network-controller>@metamask/eth-json-rpc-provider": true, + "@metamask/network-controller>@metamask/swappable-obj-proxy": true, + "@metamask/network-controller>eth-block-tracker": true, + "@metamask/utils": true, + "browserify>assert": true, + "eth-query": true, + "eth-rpc-errors": true, + "json-rpc-engine": true, + "uuid": true + } + }, + "@metamask/network-controller>@metamask/eth-json-rpc-infura": { + "globals": { + "setTimeout": true + }, + "packages": { + "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": true, + "@metamask/network-controller>@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": true, + "eth-rpc-errors": true, + "json-rpc-engine": true, + "node-fetch": true + } + }, + "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, + "@metamask/network-controller>@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": { + "globals": { + "URL": true, + "btoa": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true, + "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": true, + "@metamask/network-controller>@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>pify": true, + "@metamask/safe-event-emitter": true, + "browserify>browser-resolve": true, + "eth-rpc-errors": true, + "json-rpc-engine": true, + "lavamoat>json-stable-stringify": true, + "vinyl>clone": true + } + }, + "@metamask/network-controller>@metamask/eth-json-rpc-provider": { + "packages": { + "@metamask/safe-event-emitter": true, + "json-rpc-engine": true + } + }, + "@metamask/network-controller>eth-block-tracker": { + "globals": { + "clearTimeout": true, + "console.error": true, + "setTimeout": true + }, + "packages": { + "@metamask/network-controller>eth-block-tracker>@metamask/safe-event-emitter": true, + "@metamask/network-controller>eth-block-tracker>pify": true, + "@metamask/utils": true, + "eth-query>json-rpc-random-id": true + } + }, + "@metamask/network-controller>eth-block-tracker>@metamask/safe-event-emitter": { + "globals": { + "setTimeout": true + }, + "packages": { + "browserify>events": true + } + }, "@metamask/notification-controller>nanoid": { "globals": { "crypto.getRandomValues": true @@ -2972,27 +3016,6 @@ "postMessage": true } }, - "eth-block-tracker": { - "globals": { - "clearTimeout": true, - "console.error": true, - "setTimeout": true - }, - "packages": { - "@metamask/utils": true, - "eth-block-tracker>@metamask/safe-event-emitter": true, - "eth-block-tracker>pify": true, - "eth-query>json-rpc-random-id": true - } - }, - "eth-block-tracker>@metamask/safe-event-emitter": { - "globals": { - "setTimeout": true - }, - "packages": { - "browserify>events": true - } - }, "eth-ens-namehash": { "globals": { "name": "write" diff --git a/lavamoat/browserify/desktop/policy.json b/lavamoat/browserify/desktop/policy.json index d470098a1..fafcfcc40 100644 --- a/lavamoat/browserify/desktop/policy.json +++ b/lavamoat/browserify/desktop/policy.json @@ -976,50 +976,6 @@ "define": true } }, - "@metamask/eth-json-rpc-infura": { - "globals": { - "setTimeout": true - }, - "packages": { - "@metamask/eth-json-rpc-infura>@metamask/utils": true, - "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": true, - "eth-rpc-errors": true, - "json-rpc-engine": true, - "node-fetch": true - } - }, - "@metamask/eth-json-rpc-infura>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "browserify>buffer": true, - "nock>debug": true, - "semver": true, - "superstruct": 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>@metamask/utils": true, - "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>pify": true, - "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true, - "@metamask/safe-event-emitter": true, - "browserify>browser-resolve": true, - "eth-rpc-errors": true, - "json-rpc-engine": true, - "lavamoat>json-stable-stringify": true, - "vinyl>clone": true - } - }, "@metamask/eth-json-rpc-middleware": { "globals": { "URL": true, @@ -1048,12 +1004,6 @@ "superstruct": true } }, - "@metamask/eth-json-rpc-provider": { - "packages": { - "@metamask/safe-event-emitter": true, - "json-rpc-engine": true - } - }, "@metamask/eth-keyring-controller": { "packages": { "@metamask/browser-passworder": true, @@ -1659,6 +1609,100 @@ "browserify>url": true } }, + "@metamask/network-controller": { + "globals": { + "URL": true, + "btoa": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/base-controller": true, + "@metamask/controller-utils": true, + "@metamask/eth-json-rpc-middleware": true, + "@metamask/network-controller>@metamask/eth-json-rpc-infura": true, + "@metamask/network-controller>@metamask/eth-json-rpc-provider": true, + "@metamask/network-controller>@metamask/swappable-obj-proxy": true, + "@metamask/network-controller>eth-block-tracker": true, + "@metamask/utils": true, + "browserify>assert": true, + "eth-query": true, + "eth-rpc-errors": true, + "json-rpc-engine": true, + "uuid": true + } + }, + "@metamask/network-controller>@metamask/eth-json-rpc-infura": { + "globals": { + "setTimeout": true + }, + "packages": { + "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": true, + "@metamask/network-controller>@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": true, + "eth-rpc-errors": true, + "json-rpc-engine": true, + "node-fetch": true + } + }, + "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, + "@metamask/network-controller>@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": { + "globals": { + "URL": true, + "btoa": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true, + "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": true, + "@metamask/network-controller>@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>pify": true, + "@metamask/safe-event-emitter": true, + "browserify>browser-resolve": true, + "eth-rpc-errors": true, + "json-rpc-engine": true, + "lavamoat>json-stable-stringify": true, + "vinyl>clone": true + } + }, + "@metamask/network-controller>@metamask/eth-json-rpc-provider": { + "packages": { + "@metamask/safe-event-emitter": true, + "json-rpc-engine": true + } + }, + "@metamask/network-controller>eth-block-tracker": { + "globals": { + "clearTimeout": true, + "console.error": true, + "setTimeout": true + }, + "packages": { + "@metamask/network-controller>eth-block-tracker>@metamask/safe-event-emitter": true, + "@metamask/network-controller>eth-block-tracker>pify": true, + "@metamask/utils": true, + "eth-query>json-rpc-random-id": true + } + }, + "@metamask/network-controller>eth-block-tracker>@metamask/safe-event-emitter": { + "globals": { + "setTimeout": true + }, + "packages": { + "browserify>events": true + } + }, "@metamask/notification-controller": { "packages": { "@metamask/base-controller": true, @@ -3498,27 +3542,6 @@ "postMessage": true } }, - "eth-block-tracker": { - "globals": { - "clearTimeout": true, - "console.error": true, - "setTimeout": true - }, - "packages": { - "@metamask/utils": true, - "eth-block-tracker>@metamask/safe-event-emitter": true, - "eth-block-tracker>pify": true, - "eth-query>json-rpc-random-id": true - } - }, - "eth-block-tracker>@metamask/safe-event-emitter": { - "globals": { - "setTimeout": true - }, - "packages": { - "browserify>events": true - } - }, "eth-ens-namehash": { "globals": { "name": "write" diff --git a/lavamoat/browserify/flask/policy.json b/lavamoat/browserify/flask/policy.json index d470098a1..fafcfcc40 100644 --- a/lavamoat/browserify/flask/policy.json +++ b/lavamoat/browserify/flask/policy.json @@ -976,50 +976,6 @@ "define": true } }, - "@metamask/eth-json-rpc-infura": { - "globals": { - "setTimeout": true - }, - "packages": { - "@metamask/eth-json-rpc-infura>@metamask/utils": true, - "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": true, - "eth-rpc-errors": true, - "json-rpc-engine": true, - "node-fetch": true - } - }, - "@metamask/eth-json-rpc-infura>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "browserify>buffer": true, - "nock>debug": true, - "semver": true, - "superstruct": 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>@metamask/utils": true, - "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>pify": true, - "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true, - "@metamask/safe-event-emitter": true, - "browserify>browser-resolve": true, - "eth-rpc-errors": true, - "json-rpc-engine": true, - "lavamoat>json-stable-stringify": true, - "vinyl>clone": true - } - }, "@metamask/eth-json-rpc-middleware": { "globals": { "URL": true, @@ -1048,12 +1004,6 @@ "superstruct": true } }, - "@metamask/eth-json-rpc-provider": { - "packages": { - "@metamask/safe-event-emitter": true, - "json-rpc-engine": true - } - }, "@metamask/eth-keyring-controller": { "packages": { "@metamask/browser-passworder": true, @@ -1659,6 +1609,100 @@ "browserify>url": true } }, + "@metamask/network-controller": { + "globals": { + "URL": true, + "btoa": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/base-controller": true, + "@metamask/controller-utils": true, + "@metamask/eth-json-rpc-middleware": true, + "@metamask/network-controller>@metamask/eth-json-rpc-infura": true, + "@metamask/network-controller>@metamask/eth-json-rpc-provider": true, + "@metamask/network-controller>@metamask/swappable-obj-proxy": true, + "@metamask/network-controller>eth-block-tracker": true, + "@metamask/utils": true, + "browserify>assert": true, + "eth-query": true, + "eth-rpc-errors": true, + "json-rpc-engine": true, + "uuid": true + } + }, + "@metamask/network-controller>@metamask/eth-json-rpc-infura": { + "globals": { + "setTimeout": true + }, + "packages": { + "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": true, + "@metamask/network-controller>@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": true, + "eth-rpc-errors": true, + "json-rpc-engine": true, + "node-fetch": true + } + }, + "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, + "@metamask/network-controller>@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": { + "globals": { + "URL": true, + "btoa": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true, + "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": true, + "@metamask/network-controller>@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>pify": true, + "@metamask/safe-event-emitter": true, + "browserify>browser-resolve": true, + "eth-rpc-errors": true, + "json-rpc-engine": true, + "lavamoat>json-stable-stringify": true, + "vinyl>clone": true + } + }, + "@metamask/network-controller>@metamask/eth-json-rpc-provider": { + "packages": { + "@metamask/safe-event-emitter": true, + "json-rpc-engine": true + } + }, + "@metamask/network-controller>eth-block-tracker": { + "globals": { + "clearTimeout": true, + "console.error": true, + "setTimeout": true + }, + "packages": { + "@metamask/network-controller>eth-block-tracker>@metamask/safe-event-emitter": true, + "@metamask/network-controller>eth-block-tracker>pify": true, + "@metamask/utils": true, + "eth-query>json-rpc-random-id": true + } + }, + "@metamask/network-controller>eth-block-tracker>@metamask/safe-event-emitter": { + "globals": { + "setTimeout": true + }, + "packages": { + "browserify>events": true + } + }, "@metamask/notification-controller": { "packages": { "@metamask/base-controller": true, @@ -3498,27 +3542,6 @@ "postMessage": true } }, - "eth-block-tracker": { - "globals": { - "clearTimeout": true, - "console.error": true, - "setTimeout": true - }, - "packages": { - "@metamask/utils": true, - "eth-block-tracker>@metamask/safe-event-emitter": true, - "eth-block-tracker>pify": true, - "eth-query>json-rpc-random-id": true - } - }, - "eth-block-tracker>@metamask/safe-event-emitter": { - "globals": { - "setTimeout": true - }, - "packages": { - "browserify>events": true - } - }, "eth-ens-namehash": { "globals": { "name": "write" diff --git a/lavamoat/browserify/main/policy.json b/lavamoat/browserify/main/policy.json index 929c69e1b..494ce3924 100644 --- a/lavamoat/browserify/main/policy.json +++ b/lavamoat/browserify/main/policy.json @@ -905,50 +905,6 @@ "fetch": true } }, - "@metamask/eth-json-rpc-infura": { - "globals": { - "setTimeout": true - }, - "packages": { - "@metamask/eth-json-rpc-infura>@metamask/utils": true, - "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": true, - "eth-rpc-errors": true, - "json-rpc-engine": true, - "node-fetch": true - } - }, - "@metamask/eth-json-rpc-infura>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "browserify>buffer": true, - "nock>debug": true, - "semver": true, - "superstruct": 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>@metamask/utils": true, - "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>pify": true, - "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true, - "@metamask/safe-event-emitter": true, - "browserify>browser-resolve": true, - "eth-rpc-errors": true, - "json-rpc-engine": true, - "lavamoat>json-stable-stringify": true, - "vinyl>clone": true - } - }, "@metamask/eth-json-rpc-middleware": { "globals": { "URL": true, @@ -977,12 +933,6 @@ "superstruct": true } }, - "@metamask/eth-json-rpc-provider": { - "packages": { - "@metamask/safe-event-emitter": true, - "json-rpc-engine": true - } - }, "@metamask/eth-keyring-controller": { "packages": { "@metamask/browser-passworder": true, @@ -1588,6 +1538,100 @@ "browserify>url": true } }, + "@metamask/network-controller": { + "globals": { + "URL": true, + "btoa": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/base-controller": true, + "@metamask/controller-utils": true, + "@metamask/eth-json-rpc-middleware": true, + "@metamask/network-controller>@metamask/eth-json-rpc-infura": true, + "@metamask/network-controller>@metamask/eth-json-rpc-provider": true, + "@metamask/network-controller>@metamask/swappable-obj-proxy": true, + "@metamask/network-controller>eth-block-tracker": true, + "@metamask/utils": true, + "browserify>assert": true, + "eth-query": true, + "eth-rpc-errors": true, + "json-rpc-engine": true, + "uuid": true + } + }, + "@metamask/network-controller>@metamask/eth-json-rpc-infura": { + "globals": { + "setTimeout": true + }, + "packages": { + "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": true, + "@metamask/network-controller>@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": true, + "eth-rpc-errors": true, + "json-rpc-engine": true, + "node-fetch": true + } + }, + "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, + "@metamask/network-controller>@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": { + "globals": { + "URL": true, + "btoa": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true, + "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": true, + "@metamask/network-controller>@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>pify": true, + "@metamask/safe-event-emitter": true, + "browserify>browser-resolve": true, + "eth-rpc-errors": true, + "json-rpc-engine": true, + "lavamoat>json-stable-stringify": true, + "vinyl>clone": true + } + }, + "@metamask/network-controller>@metamask/eth-json-rpc-provider": { + "packages": { + "@metamask/safe-event-emitter": true, + "json-rpc-engine": true + } + }, + "@metamask/network-controller>eth-block-tracker": { + "globals": { + "clearTimeout": true, + "console.error": true, + "setTimeout": true + }, + "packages": { + "@metamask/network-controller>eth-block-tracker>@metamask/safe-event-emitter": true, + "@metamask/network-controller>eth-block-tracker>pify": true, + "@metamask/utils": true, + "eth-query>json-rpc-random-id": true + } + }, + "@metamask/network-controller>eth-block-tracker>@metamask/safe-event-emitter": { + "globals": { + "setTimeout": true + }, + "packages": { + "browserify>events": true + } + }, "@metamask/notification-controller>nanoid": { "globals": { "crypto.getRandomValues": true @@ -2972,27 +3016,6 @@ "postMessage": true } }, - "eth-block-tracker": { - "globals": { - "clearTimeout": true, - "console.error": true, - "setTimeout": true - }, - "packages": { - "@metamask/utils": true, - "eth-block-tracker>@metamask/safe-event-emitter": true, - "eth-block-tracker>pify": true, - "eth-query>json-rpc-random-id": true - } - }, - "eth-block-tracker>@metamask/safe-event-emitter": { - "globals": { - "setTimeout": true - }, - "packages": { - "browserify>events": true - } - }, "eth-ens-namehash": { "globals": { "name": "write" diff --git a/lavamoat/browserify/mmi/policy.json b/lavamoat/browserify/mmi/policy.json index 22627efff..6f388ca84 100644 --- a/lavamoat/browserify/mmi/policy.json +++ b/lavamoat/browserify/mmi/policy.json @@ -1126,50 +1126,6 @@ "fetch": true } }, - "@metamask/eth-json-rpc-infura": { - "globals": { - "setTimeout": true - }, - "packages": { - "@metamask/eth-json-rpc-infura>@metamask/utils": true, - "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": true, - "eth-rpc-errors": true, - "json-rpc-engine": true, - "node-fetch": true - } - }, - "@metamask/eth-json-rpc-infura>@metamask/utils": { - "globals": { - "TextDecoder": true, - "TextEncoder": true - }, - "packages": { - "browserify>buffer": true, - "nock>debug": true, - "semver": true, - "superstruct": 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>@metamask/utils": true, - "@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>pify": true, - "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true, - "@metamask/safe-event-emitter": true, - "browserify>browser-resolve": true, - "eth-rpc-errors": true, - "json-rpc-engine": true, - "lavamoat>json-stable-stringify": true, - "vinyl>clone": true - } - }, "@metamask/eth-json-rpc-middleware": { "globals": { "URL": true, @@ -1198,12 +1154,6 @@ "superstruct": true } }, - "@metamask/eth-json-rpc-provider": { - "packages": { - "@metamask/safe-event-emitter": true, - "json-rpc-engine": true - } - }, "@metamask/eth-keyring-controller": { "packages": { "@metamask/browser-passworder": true, @@ -1809,6 +1759,100 @@ "browserify>url": true } }, + "@metamask/network-controller": { + "globals": { + "URL": true, + "btoa": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/base-controller": true, + "@metamask/controller-utils": true, + "@metamask/eth-json-rpc-middleware": true, + "@metamask/network-controller>@metamask/eth-json-rpc-infura": true, + "@metamask/network-controller>@metamask/eth-json-rpc-provider": true, + "@metamask/network-controller>@metamask/swappable-obj-proxy": true, + "@metamask/network-controller>eth-block-tracker": true, + "@metamask/utils": true, + "browserify>assert": true, + "eth-query": true, + "eth-rpc-errors": true, + "json-rpc-engine": true, + "uuid": true + } + }, + "@metamask/network-controller>@metamask/eth-json-rpc-infura": { + "globals": { + "setTimeout": true + }, + "packages": { + "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": true, + "@metamask/network-controller>@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": true, + "eth-rpc-errors": true, + "json-rpc-engine": true, + "node-fetch": true + } + }, + "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": { + "globals": { + "TextDecoder": true, + "TextEncoder": true + }, + "packages": { + "browserify>buffer": true, + "nock>debug": true, + "semver": true, + "superstruct": true + } + }, + "@metamask/network-controller>@metamask/eth-json-rpc-infura>eth-json-rpc-middleware": { + "globals": { + "URL": true, + "btoa": true, + "console.error": true, + "fetch": true, + "setTimeout": true + }, + "packages": { + "@metamask/eth-trezor-keyring>@metamask/eth-sig-util": true, + "@metamask/network-controller>@metamask/eth-json-rpc-infura>@metamask/utils": true, + "@metamask/network-controller>@metamask/eth-json-rpc-infura>eth-json-rpc-middleware>pify": true, + "@metamask/safe-event-emitter": true, + "browserify>browser-resolve": true, + "eth-rpc-errors": true, + "json-rpc-engine": true, + "lavamoat>json-stable-stringify": true, + "vinyl>clone": true + } + }, + "@metamask/network-controller>@metamask/eth-json-rpc-provider": { + "packages": { + "@metamask/safe-event-emitter": true, + "json-rpc-engine": true + } + }, + "@metamask/network-controller>eth-block-tracker": { + "globals": { + "clearTimeout": true, + "console.error": true, + "setTimeout": true + }, + "packages": { + "@metamask/network-controller>eth-block-tracker>@metamask/safe-event-emitter": true, + "@metamask/network-controller>eth-block-tracker>pify": true, + "@metamask/utils": true, + "eth-query>json-rpc-random-id": true + } + }, + "@metamask/network-controller>eth-block-tracker>@metamask/safe-event-emitter": { + "globals": { + "setTimeout": true + }, + "packages": { + "browserify>events": true + } + }, "@metamask/notification-controller>nanoid": { "globals": { "crypto.getRandomValues": true @@ -3193,27 +3237,6 @@ "postMessage": true } }, - "eth-block-tracker": { - "globals": { - "clearTimeout": true, - "console.error": true, - "setTimeout": true - }, - "packages": { - "@metamask/utils": true, - "eth-block-tracker>@metamask/safe-event-emitter": true, - "eth-block-tracker>pify": true, - "eth-query>json-rpc-random-id": true - } - }, - "eth-block-tracker>@metamask/safe-event-emitter": { - "globals": { - "setTimeout": true - }, - "packages": { - "browserify>events": true - } - }, "eth-ens-namehash": { "globals": { "name": "write" diff --git a/package.json b/package.json index 8ccf3b50c..f470e45d7 100644 --- a/package.json +++ b/package.json @@ -234,9 +234,7 @@ "@metamask/controller-utils": "^4.0.0", "@metamask/design-tokens": "^1.9.0", "@metamask/desktop": "^0.3.0", - "@metamask/eth-json-rpc-infura": "^8.1.0", "@metamask/eth-json-rpc-middleware": "^11.0.0", - "@metamask/eth-json-rpc-provider": "^1.0.0", "@metamask/eth-keyring-controller": "^10.0.1", "@metamask/eth-ledger-bridge-keyring": "^0.15.0", "@metamask/eth-token-tracker": "^4.0.0", @@ -248,6 +246,7 @@ "@metamask/logo": "^3.1.1", "@metamask/message-manager": "^7.0.0", "@metamask/metamask-eth-abis": "^3.0.0", + "@metamask/network-controller": "^10.1.0", "@metamask/notification-controller": "^3.0.0", "@metamask/obs-store": "^8.1.0", "@metamask/permission-controller": "^4.0.0", @@ -269,7 +268,6 @@ "@metamask/snaps-utils": "^0.32.2", "@metamask/snaps-utils-flask": "npm:@metamask/snaps-utils@0.34.0-flask.1", "@metamask/subject-metadata-controller": "^2.0.0", - "@metamask/swappable-obj-proxy": "^2.1.0", "@metamask/utils": "^5.0.0", "@ngraveio/bc-ur": "^1.1.6", "@popperjs/core": "^2.4.0", @@ -295,7 +293,6 @@ "debounce-stream": "^2.0.0", "deep-freeze-strict": "1.1.1", "end-of-stream": "^1.4.4", - "eth-block-tracker": "^7.0.0", "eth-ens-namehash": "^2.0.8", "eth-json-rpc-filters": "^6.0.0", "eth-lattice-keyring": "^0.12.4", @@ -374,7 +371,6 @@ "@babel/preset-typescript": "^7.16.7", "@babel/register": "^7.5.5", "@ethersproject/bignumber": "^5.7.0", - "@json-rpc-specification/meta-schema": "^1.0.6", "@lavamoat/allow-scripts": "^2.0.3", "@lavamoat/lavapack": "^5.0.0", "@metamask/auto-changelog": "^2.1.0", @@ -417,7 +413,6 @@ "@types/gulp-sass": "^5.0.0", "@types/gulp-sourcemaps": "^0.0.35", "@types/jest": "^29.1.2", - "@types/jest-when": "^3.5.2", "@types/madge": "^5.0.0", "@types/node": "^17.0.21", "@types/pify": "^5.0.1", @@ -496,7 +491,6 @@ "jest": "^29.1.2", "jest-canvas-mock": "^2.3.1", "jest-environment-jsdom": "^29.1.2", - "jest-when": "^3.5.2", "js-yaml": "^4.1.0", "jsdom": "^11.2.0", "junit-report-merger": "^4.0.0", diff --git a/yarn.lock b/yarn.lock index 2ac67adc8..7e19652e3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3450,13 +3450,6 @@ __metadata: languageName: node linkType: hard -"@json-rpc-specification/meta-schema@npm:^1.0.6": - version: 1.0.6 - resolution: "@json-rpc-specification/meta-schema@npm:1.0.6" - checksum: 2eb9c6c6c73bb38350c7180d1ad3c5b8462406926cae753741895b457d7b1b9f0b74148daf3462bb167cef39efdd1d9090308edf4d4938956863acb643c146eb - languageName: node - linkType: hard - "@juggle/resize-observer@npm:^3.3.1": version: 3.4.0 resolution: "@juggle/resize-observer@npm:3.4.0" @@ -4142,7 +4135,7 @@ __metadata: languageName: node linkType: hard -"@metamask/eth-json-rpc-infura@npm:^8.0.0, @metamask/eth-json-rpc-infura@npm:^8.1.0": +"@metamask/eth-json-rpc-infura@npm:^8.0.0": version: 8.1.0 resolution: "@metamask/eth-json-rpc-infura@npm:8.1.0" dependencies: @@ -7740,15 +7733,6 @@ __metadata: languageName: node linkType: hard -"@types/jest-when@npm:^3.5.2": - version: 3.5.2 - resolution: "@types/jest-when@npm:3.5.2" - dependencies: - "@types/jest": "*" - checksum: 106230dd71ee266bbd7620ab339a7305054e56ba7638f0eff9f222e67a959df0ad68a7f0108156c50f7005881bae59cd5c38b3760c101d060cdb3dac9cd77ee2 - languageName: node - linkType: hard - "@types/jest@npm:*, @types/jest@npm:^29.1.2": version: 29.1.2 resolution: "@types/jest@npm:29.1.2" @@ -21963,15 +21947,6 @@ __metadata: languageName: node linkType: hard -"jest-when@npm:^3.5.2": - version: 3.5.2 - resolution: "jest-when@npm:3.5.2" - peerDependencies: - jest: ">= 25" - checksum: 9ad95552d377ef4d517c96a14c38bd8626d6856f518e7efc8a01d76a45a39d3c52281392c98da781c302114c78cd1ea17556c7f638d49d15971fcce9d58306e8 - languageName: node - linkType: hard - "jest-worker@npm:^27.4.5": version: 27.5.1 resolution: "jest-worker@npm:27.5.1" @@ -24309,7 +24284,6 @@ __metadata: "@ethersproject/providers": ^5.7.2 "@formatjs/intl-relativetimeformat": ^5.2.6 "@fortawesome/fontawesome-free": ^5.13.0 - "@json-rpc-specification/meta-schema": ^1.0.6 "@keystonehq/bc-ur-registry-eth": ^0.19.1 "@keystonehq/metamask-airgapped-keyring": ^0.13.1 "@lavamoat/allow-scripts": ^2.0.3 @@ -24340,9 +24314,7 @@ __metadata: "@metamask/eslint-config-mocha": ^9.0.0 "@metamask/eslint-config-nodejs": ^9.0.0 "@metamask/eslint-config-typescript": ^9.0.1 - "@metamask/eth-json-rpc-infura": ^8.1.0 "@metamask/eth-json-rpc-middleware": ^11.0.0 - "@metamask/eth-json-rpc-provider": ^1.0.0 "@metamask/eth-keyring-controller": ^10.0.1 "@metamask/eth-ledger-bridge-keyring": ^0.15.0 "@metamask/eth-token-tracker": ^4.0.0 @@ -24355,6 +24327,7 @@ __metadata: "@metamask/logo": ^3.1.1 "@metamask/message-manager": ^7.0.0 "@metamask/metamask-eth-abis": ^3.0.0 + "@metamask/network-controller": ^10.1.0 "@metamask/notification-controller": ^3.0.0 "@metamask/obs-store": ^8.1.0 "@metamask/permission-controller": ^4.0.0 @@ -24377,7 +24350,6 @@ __metadata: "@metamask/snaps-utils": ^0.32.2 "@metamask/snaps-utils-flask": "npm:@metamask/snaps-utils@0.34.0-flask.1" "@metamask/subject-metadata-controller": ^2.0.0 - "@metamask/swappable-obj-proxy": ^2.1.0 "@metamask/test-dapp": ^7.0.0 "@metamask/utils": ^5.0.0 "@ngraveio/bc-ur": ^1.1.6 @@ -24421,7 +24393,6 @@ __metadata: "@types/gulp-sass": ^5.0.0 "@types/gulp-sourcemaps": ^0.0.35 "@types/jest": ^29.1.2 - "@types/jest-when": ^3.5.2 "@types/madge": ^5.0.0 "@types/node": ^17.0.21 "@types/pify": ^5.0.1 @@ -24486,7 +24457,6 @@ __metadata: eslint-plugin-react: ^7.23.1 eslint-plugin-react-hooks: ^4.2.0 eslint-plugin-storybook: ^0.6.12 - eth-block-tracker: ^7.0.0 eth-ens-namehash: ^2.0.8 eth-json-rpc-filters: ^6.0.0 eth-lattice-keyring: ^0.12.4 @@ -24537,7 +24507,6 @@ __metadata: jest-canvas-mock: ^2.3.1 jest-environment-jsdom: ^29.1.2 jest-junit: ^14.0.1 - jest-when: ^3.5.2 js-yaml: ^4.1.0 jsdom: ^11.2.0 json-rpc-engine: ^6.1.0