From 332b55778dbbfb64c5a11edaff96d8f83475ac92 Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Tue, 12 Aug 2025 16:07:42 -0600 Subject: [PATCH 01/10] chore(network-controller): Remove lookupNetworkByClientId; refactor lookupNetwork A while back, `lookupNetworkByClientId` was added a while back, presumably as a multichain-compatible replacement of `lookupNetwork`. However, `lookupNetwork` already takes an (optional) network client ID; in fact, it calls `lookupNetworkByClientId` internally. This duplication is confusing, and it would make for a simpler API and make NetworkController more maintainable if we removed `lookupNetworkByClientId` entirely. This commit does that and refactors `lookupNetwork` to be more understandable. It also ensures that `lookupNetwork` is well-tested both when given a network client ID and not given one. --- packages/network-controller/CHANGELOG.md | 5 + .../src/NetworkController.ts | 248 +- .../tests/NetworkController.test.ts | 2122 +++++++++-------- 3 files changed, 1297 insertions(+), 1078 deletions(-) diff --git a/packages/network-controller/CHANGELOG.md b/packages/network-controller/CHANGELOG.md index bbca0d5d939..3eeddabc445 100644 --- a/packages/network-controller/CHANGELOG.md +++ b/packages/network-controller/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Removed + +- **BREAKING:** Remove `lookupNetworkByClientId` ([#6308](https://github.com/MetaMask/core/pull/6308)) + - `lookupNetwork` already supports passing in a network client ID; please use this instead. + ## [24.1.0] ### Added diff --git a/packages/network-controller/src/NetworkController.ts b/packages/network-controller/src/NetworkController.ts index ceaef81f810..5ff37e39afd 100644 --- a/packages/network-controller/src/NetworkController.ts +++ b/packages/network-controller/src/NetworkController.ts @@ -1558,25 +1558,36 @@ export class NetworkController extends BaseController< } /** - * Refreshes the network meta with EIP-1559 support and the network status - * based on the given network client ID. + * Uses a request for the latest block to gather the following information on + * the given network: * - * @param networkClientId - The ID of the network client to update. + * - The connectivity status: whether it is available, geo-blocked (Infura + * only), unavailable, or unknown + * - The capabilities status: whether it supports EIP-1559, whether it does + * not, or whether it is unknown + * + * @param networkClientId - The ID of the network client to inspect. + * If no ID is provided, uses the currently selected network. + * @returns The resulting metadata for the network. */ - async lookupNetworkByClientId(networkClientId: NetworkClientId) { - const isInfura = isInfuraNetworkType(networkClientId); - let updatedNetworkStatus: NetworkStatus; - let updatedIsEIP1559Compatible: boolean | undefined; + async #determineNetworkMetadata(networkClientId: NetworkClientId) { + // Force TypeScript to use one of the two overloads explicitly + const networkClient = isInfuraNetworkType(networkClientId) + ? this.getNetworkClientById(networkClientId) + : this.getNetworkClientById(networkClientId); + + const isInfura = + networkClient.configuration.type === NetworkClientType.Infura; + let networkStatus: NetworkStatus; + let isEIP1559Compatible: boolean | undefined; try { - updatedIsEIP1559Compatible = + isEIP1559Compatible = await this.#determineEIP1559Compatibility(networkClientId); - updatedNetworkStatus = NetworkStatus.Available; + networkStatus = NetworkStatus.Available; } catch (error) { - debugLog('NetworkController: lookupNetworkByClientId: ', error); + debugLog('NetworkController: lookupNetwork: ', error); - // TODO: mock ethQuery.sendAsync to throw error without error code - /* istanbul ignore else */ if (isErrorWithCode(error)) { let responseBody; if ( @@ -1589,7 +1600,7 @@ export class NetworkController extends BaseController< } catch { // error.message must not be JSON this.#log?.warn( - 'NetworkController: lookupNetworkByClientId: json parse error: ', + 'NetworkController: lookupNetwork: json parse error: ', error, ); } @@ -1599,84 +1610,85 @@ export class NetworkController extends BaseController< isPlainObject(responseBody) && responseBody.error === INFURA_BLOCKED_KEY ) { - updatedNetworkStatus = NetworkStatus.Blocked; + networkStatus = NetworkStatus.Blocked; } else if (error.code === errorCodes.rpc.internal) { - updatedNetworkStatus = NetworkStatus.Unknown; + networkStatus = NetworkStatus.Unknown; this.#log?.warn( - 'NetworkController: lookupNetworkByClientId: rpc internal error: ', + 'NetworkController: lookupNetwork: rpc internal error: ', error, ); } else { - updatedNetworkStatus = NetworkStatus.Unavailable; - this.#log?.warn( - 'NetworkController: lookupNetworkByClientId: ', - error, - ); + networkStatus = NetworkStatus.Unavailable; + this.#log?.warn('NetworkController: lookupNetwork: ', error); } - } else if ( - typeof Error !== 'undefined' && - hasProperty(error as unknown as Error, 'message') && - typeof (error as unknown as Error).message === 'string' && - (error as unknown as Error).message.includes( - 'No custom network client was found with the ID', - ) - ) { - throw error; } else { debugLog( 'NetworkController - could not determine network status', error, ); - updatedNetworkStatus = NetworkStatus.Unknown; - this.#log?.warn('NetworkController: lookupNetworkByClientId: ', error); + networkStatus = NetworkStatus.Unknown; + this.#log?.warn('NetworkController: lookupNetwork: ', error); } } - this.update((state) => { - if (state.networksMetadata[networkClientId] === undefined) { - state.networksMetadata[networkClientId] = { - status: NetworkStatus.Unknown, - EIPS: {}, - }; - } - const meta = state.networksMetadata[networkClientId]; - meta.status = updatedNetworkStatus; - if (updatedIsEIP1559Compatible === undefined) { - delete meta.EIPS[1559]; - } else { - meta.EIPS[1559] = updatedIsEIP1559Compatible; - } - }); + + return { isInfura, networkStatus, isEIP1559Compatible }; } /** - * Persists the following metadata about the given or selected network to - * state: - * - * - The status of the network, namely, whether it is available, geo-blocked - * (Infura only), or unavailable, or whether the status is unknown - * - Whether the network supports EIP-1559, or whether it is unknown + * Uses a request for the latest block to gather the following information on + * the given or selected network, persisting it to state: * - * Note that it is possible for the network to be switched while this data is - * being collected. If that is the case, no metadata for the (now previously) - * selected network will be updated. + * - The connectivity status: whether it is available, geo-blocked (Infura + * only), unavailable, or unknown + * - The capabilities status: whether it supports EIP-1559, whether it does + * not, or whether it is unknown * - * @param networkClientId - The ID of the network client to update. + * @param networkClientId - The ID of the network client to inspect. * If no ID is provided, uses the currently selected network. */ async lookupNetwork(networkClientId?: NetworkClientId) { if (networkClientId) { - await this.lookupNetworkByClientId(networkClientId); - return; - } - - if (!this.#ethQuery) { - return; + await this.#lookupGivenNetwork(networkClientId); + } else { + await this.#lookupSelectedNetwork(); } + } - const isInfura = - this.#autoManagedNetworkClient?.configuration.type === - NetworkClientType.Infura; + /** + * Uses a request for the latest block to gather the following information on + * the given network, persisting it to state: + * + * - The connectivity status: whether the network is available, geo-blocked + * (Infura only), unavailable, or unknown + * - The feature compatibility status: whether the network supports EIP-1559, + * whether it does not, or whether it is unknown + * + * @param networkClientId - The ID of the network client to inspect. + */ + async #lookupGivenNetwork(networkClientId: NetworkClientId) { + const { networkStatus, isEIP1559Compatible } = + await this.#determineNetworkMetadata(networkClientId); + this.#updateMetadataForNetwork( + networkClientId, + networkStatus, + isEIP1559Compatible, + ); + } + /** + * Uses a request for the latest block to gather the following information on + * the currently selected network, persisting it to state: + * + * - The connectivity status: whether the network is available, geo-blocked + * (Infura only), unavailable, or unknown + * - The feature compatibility status: whether the network supports EIP-1559, + * whether it does not, or whether it is unknown + * + * Note that it is possible for the current network to be switched while this + * method is running. If that is the case, it will exit early (as this method + * will also run for the new network). + */ + async #lookupSelectedNetwork() { let networkChanged = false; const listener = () => { networkChanged = true; @@ -1710,60 +1722,8 @@ export class NetworkController extends BaseController< listener, ); - let updatedNetworkStatus: NetworkStatus; - let updatedIsEIP1559Compatible: boolean | undefined; - - try { - const isEIP1559Compatible = await this.#determineEIP1559Compatibility( - this.state.selectedNetworkClientId, - ); - updatedNetworkStatus = NetworkStatus.Available; - updatedIsEIP1559Compatible = isEIP1559Compatible; - } catch (error) { - // TODO: mock ethQuery.sendAsync to throw error without error code - /* istanbul ignore else */ - if (isErrorWithCode(error)) { - let responseBody; - if ( - isInfura && - hasProperty(error, 'message') && - typeof error.message === 'string' - ) { - try { - responseBody = JSON.parse(error.message); - } catch (parseError) { - // error.message must not be JSON - this.#log?.warn( - 'NetworkController: lookupNetwork: json parse error', - parseError, - ); - } - } - - if ( - isPlainObject(responseBody) && - responseBody.error === INFURA_BLOCKED_KEY - ) { - updatedNetworkStatus = NetworkStatus.Blocked; - } else if (error.code === errorCodes.rpc.internal) { - updatedNetworkStatus = NetworkStatus.Unknown; - this.#log?.warn( - 'NetworkController: lookupNetwork: rpc internal error', - error, - ); - } else { - updatedNetworkStatus = NetworkStatus.Unavailable; - this.#log?.warn('NetworkController: lookupNetwork: ', error); - } - } else { - debugLog( - 'NetworkController - could not determine network status', - error, - ); - updatedNetworkStatus = NetworkStatus.Unknown; - this.#log?.warn('NetworkController: lookupNetwork: ', error); - } - } + const { isInfura, networkStatus, isEIP1559Compatible } = + await this.#determineNetworkMetadata(this.state.selectedNetworkClientId); if (networkChanged) { // If the network has changed, then `lookupNetwork` either has been or is @@ -1786,20 +1746,16 @@ export class NetworkController extends BaseController< } } - this.update((state) => { - const meta = state.networksMetadata[state.selectedNetworkClientId]; - meta.status = updatedNetworkStatus; - if (updatedIsEIP1559Compatible === undefined) { - delete meta.EIPS[1559]; - } else { - meta.EIPS[1559] = updatedIsEIP1559Compatible; - } - }); + this.#updateMetadataForNetwork( + this.state.selectedNetworkClientId, + networkStatus, + isEIP1559Compatible, + ); if (isInfura) { - if (updatedNetworkStatus === NetworkStatus.Available) { + if (networkStatus === NetworkStatus.Available) { this.messagingSystem.publish('NetworkController:infuraIsUnblocked'); - } else if (updatedNetworkStatus === NetworkStatus.Blocked) { + } else if (networkStatus === NetworkStatus.Blocked) { this.messagingSystem.publish('NetworkController:infuraIsBlocked'); } } else { @@ -1810,6 +1766,36 @@ export class NetworkController extends BaseController< } } + /** + * Updates the metadata for the given network in state. + * + * @param networkClientId - The associated network client ID. + * @param networkStatus - The network status to store in state. + * @param isEIP1559Compatible - The EIP-1559 compatibility status to + * store in state. + */ + #updateMetadataForNetwork( + networkClientId: NetworkClientId, + networkStatus: NetworkStatus, + isEIP1559Compatible: boolean | undefined, + ) { + this.update((state) => { + if (state.networksMetadata[networkClientId] === undefined) { + state.networksMetadata[networkClientId] = { + status: NetworkStatus.Unknown, + EIPS: {}, + }; + } + const meta = state.networksMetadata[networkClientId]; + meta.status = networkStatus; + if (isEIP1559Compatible === undefined) { + delete meta.EIPS[1559]; + } else { + meta.EIPS[1559] = isEIP1559Compatible; + } + }); + } + /** * Convenience method to update provider network type settings. * diff --git a/packages/network-controller/tests/NetworkController.test.ts b/packages/network-controller/tests/NetworkController.test.ts index 784b6ab79cd..70e40549d58 100644 --- a/packages/network-controller/tests/NetworkController.test.ts +++ b/packages/network-controller/tests/NetworkController.test.ts @@ -1104,6 +1104,7 @@ describe('NetworkController', () => { lookupNetworkTests({ expectedNetworkClientType: NetworkClientType.Infura, + expectedNetworkClientId: infuraNetworkType, initialState: { selectedNetworkClientId: infuraNetworkType, }, @@ -1165,6 +1166,7 @@ describe('NetworkController', () => { lookupNetworkTests({ expectedNetworkClientType: NetworkClientType.Custom, + expectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', initialState: { selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', networkConfigurationsByChainId: { @@ -1749,68 +1751,376 @@ describe('NetworkController', () => { }); describe('lookupNetwork', () => { - describe('if a networkClientId param is passed', () => { - it('updates the network status', async () => { - await withController( - { infuraProjectId: 'some-infura-project-id' }, - async ({ controller }) => { - const fakeNetworkClient = buildFakeClient(); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await controller.lookupNetwork('mainnet'); - - expect(controller.state.networksMetadata.mainnet.status).toBe( - 'available', - ); + for (const infuraNetworkType of INFURA_NETWORKS) { + describe(`given a network client ID that represents the Infura network "${infuraNetworkType}"`, () => { + lookupNetworkTests({ + expectedNetworkClientType: NetworkClientType.Infura, + expectedNetworkClientId: infuraNetworkType, + operation: async (controller) => { + await controller.lookupNetwork(infuraNetworkType); }, - ); + shouldTestInfuraMessengerEvents: false, + }); }); + } - it('throws an error if the network is not found', async () => { - await withController( - { infuraProjectId: 'some-infura-project-id' }, - async ({ controller }) => { - await expect(() => - controller.lookupNetwork('non-existent-network-id'), - ).rejects.toThrow( - 'No custom network client was found with the ID "non-existent-network-id".', - ); + describe('given a network client that represents a custom RPC endpoint', () => { + const networkClientId = 'BBBB-BBBB-BBBB-BBBB'; + + lookupNetworkTests({ + expectedNetworkClientType: NetworkClientType.Custom, + expectedNetworkClientId: networkClientId, + initialState: { + selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', + networkConfigurationsByChainId: { + '0x1337': buildCustomNetworkConfiguration({ + chainId: '0x1337', + nativeCurrency: 'TEST', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.network/1', + }), + buildCustomRpcEndpoint({ + networkClientId: 'BBBB-BBBB-BBBB-BBBB', + url: 'https://test.network/2', + }), + ], + }), }, - ); + }, + operation: async (controller) => { + await controller.lookupNetwork(networkClientId); + }, + shouldTestInfuraMessengerEvents: false, }); }); - for (const infuraNetworkType of INFURA_NETWORKS) { - const infuraChainId = ChainId[infuraNetworkType]; + describe('given an invalid network client ID', () => { + it('throws an error', async () => { + await withController(async ({ controller }) => { + await expect(() => + controller.lookupNetwork('non-existent-network-id'), + ).rejects.toThrow( + 'No custom network client was found with the ID "non-existent-network-id".', + ); + }); + }); + }); - // False negative - this is a string. - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - describe(`when the selected network client represents the Infura network "${infuraNetworkType}"`, () => { - 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 () => { - const infuraProjectId = 'some-infura-project-id'; + describe('not given a network client ID', () => { + for (const infuraNetworkType of INFURA_NETWORKS) { + const infuraChainId = ChainId[infuraNetworkType]; - await withController( - { - state: { - selectedNetworkClientId: infuraNetworkType, - networkConfigurationsByChainId: { - [infuraChainId]: - buildInfuraNetworkConfiguration(infuraNetworkType), - '0x1337': buildCustomNetworkConfiguration({ - chainId: '0x1337', - rpcEndpoints: [ - buildCustomRpcEndpoint({ - networkClientId: 'AAAA-AAAA-AAAA-AAAA', - }), - ], - }), + describe(`when the selected network client represents the Infura network "${infuraNetworkType}"`, () => { + 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 () => { + const infuraProjectId = 'some-infura-project-id'; + + await withController( + { + state: { + selectedNetworkClientId: infuraNetworkType, + networkConfigurationsByChainId: { + [infuraChainId]: + buildInfuraNetworkConfiguration(infuraNetworkType), + '0x1337': buildCustomNetworkConfiguration({ + chainId: '0x1337', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + }), + ], + }), + }, }, + infuraProjectId, }, - infuraProjectId, - }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ + async ({ controller }) => { + const fakeProviders = [ + buildFakeProvider([ + // Called during provider initialization + { + request: { + method: 'eth_getBlockByNumber', + }, + response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, + }, + // Called via `lookupNetwork` directly + { + request: { + method: 'eth_getBlockByNumber', + }, + response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, + beforeCompleting: () => { + // We are purposefully not awaiting this promise. + // eslint-disable-next-line @typescript-eslint/no-floating-promises + controller.setActiveNetwork('AAAA-AAAA-AAAA-AAAA'); + }, + }, + ]), + buildFakeProvider([ + // Called when switching networks + { + request: { + method: 'eth_getBlockByNumber', + }, + error: GENERIC_JSON_RPC_ERROR, + }, + ]), + ]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + createNetworkClientMock.mockImplementation( + ({ configuration }) => { + if (configuration.chainId === infuraChainId) { + return fakeNetworkClients[0]; + } else if (configuration.chainId === '0x1337') { + return fakeNetworkClients[1]; + } + throw new Error( + `Unknown network client configuration ${JSON.stringify( + configuration, + )}`, + ); + }, + ); + await controller.initializeProvider(); + expect( + controller.state.networksMetadata[infuraNetworkType].status, + ).toBe('available'); + + await controller.lookupNetwork(); + + expect( + controller.state.networksMetadata['AAAA-AAAA-AAAA-AAAA'] + .status, + ).toBe('unknown'); + }, + ); + }); + + it('stores the EIP-1559 support of the second network, not the first', async () => { + const infuraProjectId = 'some-infura-project-id'; + + await withController( + { + state: { + selectedNetworkClientId: infuraNetworkType, + networkConfigurationsByChainId: { + [infuraChainId]: + buildInfuraNetworkConfiguration(infuraNetworkType), + '0x1337': buildCustomNetworkConfiguration({ + chainId: '0x1337', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + }), + ], + }), + }, + }, + infuraProjectId, + }, + async ({ controller }) => { + const fakeProviders = [ + buildFakeProvider([ + // Called during provider initialization + { + request: { + method: 'eth_getBlockByNumber', + }, + response: { + result: POST_1559_BLOCK, + }, + }, + // Called via `lookupNetwork` directly + { + request: { + method: 'eth_getBlockByNumber', + }, + response: { + result: POST_1559_BLOCK, + }, + beforeCompleting: () => { + // We are purposefully not awaiting this promise. + // eslint-disable-next-line @typescript-eslint/no-floating-promises + controller.setActiveNetwork('AAAA-AAAA-AAAA-AAAA'); + }, + }, + ]), + buildFakeProvider([ + // Called when switching networks + { + request: { + method: 'eth_getBlockByNumber', + }, + response: { + result: PRE_1559_BLOCK, + }, + }, + ]), + ]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + createNetworkClientMock.mockImplementation( + ({ configuration }) => { + if (configuration.chainId === infuraChainId) { + return fakeNetworkClients[0]; + } else if (configuration.chainId === '0x1337') { + return fakeNetworkClients[1]; + } + throw new Error( + `Unknown network client configuration ${JSON.stringify( + configuration, + )}`, + ); + }, + ); + await controller.initializeProvider(); + expect( + controller.state.networksMetadata[infuraNetworkType] + .EIPS[1559], + ).toBe(true); + + await controller.lookupNetwork(); + + expect( + controller.state.networksMetadata['AAAA-AAAA-AAAA-AAAA'] + .EIPS[1559], + ).toBe(false); + }, + ); + }); + + it('emits infuraIsUnblocked, not infuraIsBlocked, assuming that the first network was blocked', async () => { + const infuraProjectId = 'some-infura-project-id'; + + await withController( + { + state: { + selectedNetworkClientId: infuraNetworkType, + networkConfigurationsByChainId: { + [infuraChainId]: + buildInfuraNetworkConfiguration(infuraNetworkType), + '0x1337': buildCustomNetworkConfiguration({ + chainId: '0x1337', + nativeCurrency: 'TEST', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.network', + }), + ], + }), + }, + }, + infuraProjectId, + }, + async ({ controller, messenger }) => { + const fakeProviders = [ + buildFakeProvider([ + // Called during provider initialization + { + request: { + method: 'eth_getBlockByNumber', + }, + response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, + }, + // Called via `lookupNetwork` directly + { + request: { + method: 'eth_getBlockByNumber', + }, + error: BLOCKED_INFURA_JSON_RPC_ERROR, + beforeCompleting: () => { + // We are purposefully not awaiting this promise. + // eslint-disable-next-line @typescript-eslint/no-floating-promises + controller.setActiveNetwork('AAAA-AAAA-AAAA-AAAA'); + }, + }, + ]), + buildFakeProvider([ + // Called when switching networks + { + request: { + method: 'eth_getBlockByNumber', + }, + response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, + }, + ]), + ]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + createNetworkClientMock.mockImplementation( + ({ configuration }) => { + if (configuration.chainId === infuraChainId) { + return fakeNetworkClients[0]; + } else if (configuration.chainId === '0x1337') { + return fakeNetworkClients[1]; + } + throw new Error( + `Unknown network client configuration ${JSON.stringify( + configuration, + )}`, + ); + }, + ); + await controller.initializeProvider(); + const promiseForInfuraIsUnblockedEvents = + waitForPublishedEvents({ + messenger, + eventType: 'NetworkController:infuraIsUnblocked', + }); + const promiseForNoInfuraIsBlockedEvents = + waitForPublishedEvents({ + messenger, + eventType: 'NetworkController:infuraIsBlocked', + count: 0, + }); + + await waitForStateChanges({ + messenger, + propertyPath: [ + 'networksMetadata', + 'AAAA-AAAA-AAAA-AAAA', + 'status', + ], + operation: async () => { + await controller.lookupNetwork(); + }, + }); + + await expect( + promiseForInfuraIsUnblockedEvents, + ).toBeFulfilled(); + await expect( + promiseForNoInfuraIsBlockedEvents, + ).toBeFulfilled(); + }, + ); + }); + }); + + describe('if all subscriptions are removed from the messenger before the call to lookupNetwork completes', () => { + it('does not throw an error', async () => { + const infuraProjectId = 'some-infura-project-id'; + + await withController( + { + state: { + selectedNetworkClientId: infuraNetworkType, + }, + infuraProjectId, + }, + async ({ controller, messenger }) => { + const fakeProvider = buildFakeProvider([ // Called during provider initialization { request: { @@ -1824,66 +2134,95 @@ describe('NetworkController', () => { method: 'eth_getBlockByNumber', }, response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - beforeCompleting: () => { - // We are purposefully not awaiting this promise. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - controller.setActiveNetwork('AAAA-AAAA-AAAA-AAAA'); + }, + ]); + const fakeNetworkClient = buildFakeClient(fakeProvider); + createNetworkClientMock.mockReturnValue(fakeNetworkClient); + await controller.initializeProvider(); + + const lookupNetworkPromise = controller.lookupNetwork(); + messenger.clearSubscriptions(); + expect(await lookupNetworkPromise).toBeUndefined(); + }, + ); + }); + }); + + describe('if removing the networkDidChange subscription fails for an unknown reason', () => { + it('re-throws the error', async () => { + const infuraProjectId = 'some-infura-project-id'; + + await withController( + { + state: { + selectedNetworkClientId: infuraNetworkType, + }, + infuraProjectId, + }, + async ({ controller, messenger }) => { + const fakeProvider = buildFakeProvider([ + // Called during provider initialization + { + request: { + method: 'eth_getBlockByNumber', }, + response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, }, - ]), - buildFakeProvider([ - // Called when switching networks + // Called via `lookupNetwork` directly { request: { method: 'eth_getBlockByNumber', }, - error: GENERIC_JSON_RPC_ERROR, + response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - createNetworkClientMock.mockImplementation( - ({ configuration }) => { - if (configuration.chainId === infuraChainId) { - return fakeNetworkClients[0]; - } else if (configuration.chainId === '0x1337') { - return fakeNetworkClients[1]; - } - throw new Error( - `Unknown network client configuration ${JSON.stringify( - configuration, - )}`, - ); - }, - ); - await controller.initializeProvider(); - expect( - controller.state.networksMetadata[infuraNetworkType].status, - ).toBe('available'); + ]); + const fakeNetworkClient = buildFakeClient(fakeProvider); + createNetworkClientMock.mockReturnValue(fakeNetworkClient); + await controller.initializeProvider(); - await controller.lookupNetwork(); + const lookupNetworkPromise = controller.lookupNetwork(); + const error = new Error('oops'); + jest + .spyOn(messenger, 'unsubscribe') + .mockImplementation((eventType) => { + // This is okay. + // eslint-disable-next-line jest/no-conditional-in-test + if (eventType === 'NetworkController:networkDidChange') { + throw error; + } + }); + await expect(lookupNetworkPromise).rejects.toThrow(error); + }, + ); + }); + }); - expect( - controller.state.networksMetadata['AAAA-AAAA-AAAA-AAAA'] - .status, - ).toBe('unknown'); - }, - ); + lookupNetworkTests({ + expectedNetworkClientType: NetworkClientType.Infura, + expectedNetworkClientId: infuraNetworkType, + initialState: { + selectedNetworkClientId: infuraNetworkType, + }, + operation: async (controller) => { + await controller.lookupNetwork(); + }, }); + }); + } - it('stores the EIP-1559 support of the second network, not the first', async () => { + describe('when the selected network client represents a custom RPC endpoint', () => { + 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 () => { const infuraProjectId = 'some-infura-project-id'; await withController( { state: { - selectedNetworkClientId: infuraNetworkType, + selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', networkConfigurationsByChainId: { - [infuraChainId]: - buildInfuraNetworkConfiguration(infuraNetworkType), + [TESTNET.chainId]: buildInfuraNetworkConfiguration( + TESTNET.networkType, + ), '0x1337': buildCustomNetworkConfiguration({ chainId: '0x1337', rpcEndpoints: [ @@ -1904,22 +2243,18 @@ describe('NetworkController', () => { request: { method: 'eth_getBlockByNumber', }, - response: { - result: POST_1559_BLOCK, - }, + response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, }, // Called via `lookupNetwork` directly { request: { method: 'eth_getBlockByNumber', }, - response: { - result: POST_1559_BLOCK, - }, + response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, beforeCompleting: () => { // We are purposefully not awaiting this promise. // eslint-disable-next-line @typescript-eslint/no-floating-promises - controller.setActiveNetwork('AAAA-AAAA-AAAA-AAAA'); + controller.setProviderType(TESTNET.networkType); }, }, ]), @@ -1929,9 +2264,7 @@ describe('NetworkController', () => { request: { method: 'eth_getBlockByNumber', }, - response: { - result: PRE_1559_BLOCK, - }, + error: GENERIC_JSON_RPC_ERROR, }, ]), ]; @@ -1941,9 +2274,11 @@ describe('NetworkController', () => { ]; createNetworkClientMock.mockImplementation( ({ configuration }) => { - if (configuration.chainId === infuraChainId) { + if (configuration.chainId === '0x1337') { return fakeNetworkClients[0]; - } else if (configuration.chainId === '0x1337') { + } else if ( + configuration.chainId === ChainId[TESTNET.networkType] + ) { return fakeNetworkClients[1]; } throw new Error( @@ -1955,37 +2290,35 @@ describe('NetworkController', () => { ); await controller.initializeProvider(); expect( - controller.state.networksMetadata[infuraNetworkType] - .EIPS[1559], - ).toBe(true); + controller.state.networksMetadata['AAAA-AAAA-AAAA-AAAA'] + .status, + ).toBe('available'); await controller.lookupNetwork(); expect( - controller.state.networksMetadata['AAAA-AAAA-AAAA-AAAA'] - .EIPS[1559], - ).toBe(false); + controller.state.networksMetadata[TESTNET.networkType].status, + ).toBe('unknown'); }, ); }); - it('emits infuraIsUnblocked, not infuraIsBlocked, assuming that the first network was blocked', async () => { + it('stores the EIP-1559 support of the second network, not the first', async () => { const infuraProjectId = 'some-infura-project-id'; await withController( { state: { - selectedNetworkClientId: infuraNetworkType, + selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', networkConfigurationsByChainId: { - [infuraChainId]: - buildInfuraNetworkConfiguration(infuraNetworkType), + [TESTNET.chainId]: buildInfuraNetworkConfiguration( + TESTNET.networkType, + ), '0x1337': buildCustomNetworkConfiguration({ chainId: '0x1337', - nativeCurrency: 'TEST', rpcEndpoints: [ buildCustomRpcEndpoint({ networkClientId: 'AAAA-AAAA-AAAA-AAAA', - url: 'https://test.network', }), ], }), @@ -1993,7 +2326,7 @@ describe('NetworkController', () => { }, infuraProjectId, }, - async ({ controller, messenger }) => { + async ({ controller }) => { const fakeProviders = [ buildFakeProvider([ // Called during provider initialization @@ -2001,18 +2334,22 @@ describe('NetworkController', () => { request: { method: 'eth_getBlockByNumber', }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, + response: { + result: POST_1559_BLOCK, + }, }, // Called via `lookupNetwork` directly { request: { method: 'eth_getBlockByNumber', }, - error: BLOCKED_INFURA_JSON_RPC_ERROR, + response: { + result: POST_1559_BLOCK, + }, beforeCompleting: () => { // We are purposefully not awaiting this promise. // eslint-disable-next-line @typescript-eslint/no-floating-promises - controller.setActiveNetwork('AAAA-AAAA-AAAA-AAAA'); + controller.setProviderType(TESTNET.networkType); }, }, ]), @@ -2022,7 +2359,9 @@ describe('NetworkController', () => { request: { method: 'eth_getBlockByNumber', }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, + response: { + result: PRE_1559_BLOCK, + }, }, ]), ]; @@ -2032,9 +2371,11 @@ describe('NetworkController', () => { ]; createNetworkClientMock.mockImplementation( ({ configuration }) => { - if (configuration.chainId === infuraChainId) { + if (configuration.chainId === '0x1337') { return fakeNetworkClients[0]; - } else if (configuration.chainId === '0x1337') { + } else if ( + configuration.chainId === ChainId[TESTNET.networkType] + ) { return fakeNetworkClients[1]; } throw new Error( @@ -2045,358 +2386,147 @@ describe('NetworkController', () => { }, ); await controller.initializeProvider(); - const promiseForInfuraIsUnblockedEvents = - waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsUnblocked', - }); - const promiseForNoInfuraIsBlockedEvents = - waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsBlocked', - count: 0, - }); + expect( + controller.state.networksMetadata['AAAA-AAAA-AAAA-AAAA'] + .EIPS[1559], + ).toBe(true); - await waitForStateChanges({ - messenger, - propertyPath: [ - 'networksMetadata', - 'AAAA-AAAA-AAAA-AAAA', - 'status', - ], - operation: async () => { - await controller.lookupNetwork(); - }, - }); + await controller.lookupNetwork(); - await expect(promiseForInfuraIsUnblockedEvents).toBeFulfilled(); - await expect(promiseForNoInfuraIsBlockedEvents).toBeFulfilled(); + expect( + controller.state.networksMetadata[TESTNET.networkType] + .EIPS[1559], + ).toBe(false); + expect( + controller.state.networksMetadata['AAAA-AAAA-AAAA-AAAA'] + .EIPS[1559], + ).toBe(true); }, ); }); - }); - describe('if all subscriptions are removed from the messenger before the call to lookupNetwork completes', () => { - it('does not throw an error', async () => { + it('emits infuraIsBlocked, not infuraIsUnblocked, if the second network was blocked and the first network was not', async () => { const infuraProjectId = 'some-infura-project-id'; await withController( { state: { - selectedNetworkClientId: infuraNetworkType, - }, - infuraProjectId, - }, - async ({ controller, messenger }) => { - const fakeProvider = buildFakeProvider([ - // Called during provider initialization - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - // Called via `lookupNetwork` directly - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, + selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', + networkConfigurationsByChainId: { + [TESTNET.chainId]: buildInfuraNetworkConfiguration( + TESTNET.networkType, + ), + '0x1337': buildCustomNetworkConfiguration({ + chainId: '0x1337', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + }), + ], + }), }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - createNetworkClientMock.mockReturnValue(fakeNetworkClient); - await controller.initializeProvider(); - - const lookupNetworkPromise = controller.lookupNetwork(); - messenger.clearSubscriptions(); - expect(await lookupNetworkPromise).toBeUndefined(); - }, - ); - }); - }); - - describe('if removing the networkDidChange subscription fails for an unknown reason', () => { - it('re-throws the error', async () => { - const infuraProjectId = 'some-infura-project-id'; - - await withController( - { - state: { - selectedNetworkClientId: infuraNetworkType, }, infuraProjectId, }, async ({ controller, messenger }) => { - const fakeProvider = buildFakeProvider([ - // Called during provider initialization - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - // Called via `lookupNetwork` directly - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - createNetworkClientMock.mockReturnValue(fakeNetworkClient); - await controller.initializeProvider(); - - const lookupNetworkPromise = controller.lookupNetwork(); - const error = new Error('oops'); - jest - .spyOn(messenger, 'unsubscribe') - .mockImplementation((eventType) => { - // This is okay. - // eslint-disable-next-line jest/no-conditional-in-test - if (eventType === 'NetworkController:networkDidChange') { - throw error; - } - }); - await expect(lookupNetworkPromise).rejects.toThrow(error); - }, - ); - }); - }); - - lookupNetworkTests({ - expectedNetworkClientType: NetworkClientType.Infura, - initialState: { - selectedNetworkClientId: infuraNetworkType, - }, - operation: async (controller) => { - await controller.lookupNetwork(); - }, - }); - }); - } - - describe('when the selected network client represents a custom RPC endpoint', () => { - 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 () => { - const infuraProjectId = 'some-infura-project-id'; - - await withController( - { - state: { - selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', - networkConfigurationsByChainId: { - [TESTNET.chainId]: buildInfuraNetworkConfiguration( - TESTNET.networkType, - ), - '0x1337': buildCustomNetworkConfiguration({ - chainId: '0x1337', - rpcEndpoints: [ - buildCustomRpcEndpoint({ - networkClientId: 'AAAA-AAAA-AAAA-AAAA', - }), - ], - }), - }, - }, - infuraProjectId, - }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ - // Called during provider initialization - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - // Called via `lookupNetwork` directly - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - beforeCompleting: () => { - // We are purposefully not awaiting this promise. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - controller.setProviderType(TESTNET.networkType); - }, - }, - ]), - buildFakeProvider([ - // Called when switching networks - { - request: { - method: 'eth_getBlockByNumber', - }, - error: GENERIC_JSON_RPC_ERROR, - }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - createNetworkClientMock.mockImplementation( - ({ configuration }) => { - if (configuration.chainId === '0x1337') { - return fakeNetworkClients[0]; - } else if ( - configuration.chainId === ChainId[TESTNET.networkType] - ) { - return fakeNetworkClients[1]; - } - throw new Error( - `Unknown network client configuration ${JSON.stringify( - configuration, - )}`, - ); - }, - ); - await controller.initializeProvider(); - expect( - controller.state.networksMetadata['AAAA-AAAA-AAAA-AAAA'].status, - ).toBe('available'); - - await controller.lookupNetwork(); - - expect( - controller.state.networksMetadata[TESTNET.networkType].status, - ).toBe('unknown'); - }, - ); - }); - - it('stores the EIP-1559 support of the second network, not the first', async () => { - const infuraProjectId = 'some-infura-project-id'; - - await withController( - { - state: { - selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', - networkConfigurationsByChainId: { - [TESTNET.chainId]: buildInfuraNetworkConfiguration( - TESTNET.networkType, - ), - '0x1337': buildCustomNetworkConfiguration({ - chainId: '0x1337', - rpcEndpoints: [ - buildCustomRpcEndpoint({ - networkClientId: 'AAAA-AAAA-AAAA-AAAA', - }), - ], - }), - }, - }, - infuraProjectId, - }, - async ({ controller }) => { - const fakeProviders = [ - buildFakeProvider([ - // Called during provider initialization - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: POST_1559_BLOCK, - }, - }, - // Called via `lookupNetwork` directly - { - request: { - method: 'eth_getBlockByNumber', - }, - response: { - result: POST_1559_BLOCK, - }, - beforeCompleting: () => { - // We are purposefully not awaiting this promise. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - controller.setProviderType(TESTNET.networkType); + const fakeProviders = [ + buildFakeProvider([ + // Called during provider initialization + { + request: { + method: 'eth_getBlockByNumber', + }, + response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, }, - }, - ]), - buildFakeProvider([ - // Called when switching networks - { - request: { - method: 'eth_getBlockByNumber', + // Called via `lookupNetwork` directly + { + request: { + method: 'eth_getBlockByNumber', + }, + response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, + beforeCompleting: () => { + // We are purposefully not awaiting this promise. + // eslint-disable-next-line @typescript-eslint/no-floating-promises + controller.setProviderType(TESTNET.networkType); + }, }, - response: { - result: PRE_1559_BLOCK, + ]), + buildFakeProvider([ + // Called when switching networks + { + request: { + method: 'eth_getBlockByNumber', + }, + error: BLOCKED_INFURA_JSON_RPC_ERROR, }, + ]), + ]; + const fakeNetworkClients = [ + buildFakeClient(fakeProviders[0]), + buildFakeClient(fakeProviders[1]), + ]; + createNetworkClientMock.mockImplementation( + ({ configuration }) => { + if (configuration.chainId === '0x1337') { + return fakeNetworkClients[0]; + } else if ( + configuration.chainId === ChainId[TESTNET.networkType] + ) { + return fakeNetworkClients[1]; + } + throw new Error( + `Unknown network client configuration ${JSON.stringify( + configuration, + )}`, + ); }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - createNetworkClientMock.mockImplementation( - ({ configuration }) => { - if (configuration.chainId === '0x1337') { - return fakeNetworkClients[0]; - } else if ( - configuration.chainId === ChainId[TESTNET.networkType] - ) { - return fakeNetworkClients[1]; - } - throw new Error( - `Unknown network client configuration ${JSON.stringify( - configuration, - )}`, - ); - }, - ); - await controller.initializeProvider(); - expect( - controller.state.networksMetadata['AAAA-AAAA-AAAA-AAAA'] - .EIPS[1559], - ).toBe(true); + ); + await controller.initializeProvider(); + const promiseForNoInfuraIsUnblockedEvents = + waitForPublishedEvents({ + messenger, + eventType: 'NetworkController:infuraIsUnblocked', + count: 0, + }); + const promiseForInfuraIsBlockedEvents = waitForPublishedEvents({ + messenger, + eventType: 'NetworkController:infuraIsBlocked', + }); - await controller.lookupNetwork(); + await controller.lookupNetwork(); - expect( - controller.state.networksMetadata[TESTNET.networkType] - .EIPS[1559], - ).toBe(false); - expect( - controller.state.networksMetadata['AAAA-AAAA-AAAA-AAAA'] - .EIPS[1559], - ).toBe(true); - }, - ); + await expect( + promiseForNoInfuraIsUnblockedEvents, + ).toBeFulfilled(); + await expect(promiseForInfuraIsBlockedEvents).toBeFulfilled(); + }, + ); + }); }); - it('emits infuraIsBlocked, not infuraIsUnblocked, if the second network was blocked and the first network was not', async () => { - const infuraProjectId = 'some-infura-project-id'; + describe('if all subscriptions are removed from the messenger before the call to lookupNetwork completes', () => { + it('does not throw an error', async () => { + const infuraProjectId = 'some-infura-project-id'; - await withController( - { - state: { - selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', - networkConfigurationsByChainId: { - [TESTNET.chainId]: buildInfuraNetworkConfiguration( - TESTNET.networkType, - ), - '0x1337': buildCustomNetworkConfiguration({ - chainId: '0x1337', - rpcEndpoints: [ - buildCustomRpcEndpoint({ - networkClientId: 'AAAA-AAAA-AAAA-AAAA', - }), - ], - }), + await withController( + { + state: { + selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', + networkConfigurationsByChainId: { + '0x1337': buildCustomNetworkConfiguration({ + chainId: '0x1337', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + }), + ], + }), + }, }, + infuraProjectId, }, - infuraProjectId, - }, - async ({ controller, messenger }) => { - const fakeProviders = [ - buildFakeProvider([ + async ({ controller, messenger }) => { + const fakeProvider = buildFakeProvider([ // Called during provider initialization { request: { @@ -2410,193 +2540,101 @@ describe('NetworkController', () => { method: 'eth_getBlockByNumber', }, response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - beforeCompleting: () => { - // We are purposefully not awaiting this promise. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - controller.setProviderType(TESTNET.networkType); - }, - }, - ]), - buildFakeProvider([ - // Called when switching networks - { - request: { - method: 'eth_getBlockByNumber', - }, - error: BLOCKED_INFURA_JSON_RPC_ERROR, }, - ]), - ]; - const fakeNetworkClients = [ - buildFakeClient(fakeProviders[0]), - buildFakeClient(fakeProviders[1]), - ]; - createNetworkClientMock.mockImplementation( - ({ configuration }) => { - if (configuration.chainId === '0x1337') { - return fakeNetworkClients[0]; - } else if ( - configuration.chainId === ChainId[TESTNET.networkType] - ) { - return fakeNetworkClients[1]; - } - throw new Error( - `Unknown network client configuration ${JSON.stringify( - configuration, - )}`, - ); - }, - ); - await controller.initializeProvider(); - const promiseForNoInfuraIsUnblockedEvents = - waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsUnblocked', - count: 0, - }); - const promiseForInfuraIsBlockedEvents = waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsBlocked', - }); - - await controller.lookupNetwork(); - - await expect(promiseForNoInfuraIsUnblockedEvents).toBeFulfilled(); - await expect(promiseForInfuraIsBlockedEvents).toBeFulfilled(); - }, - ); - }); - }); - - describe('if all subscriptions are removed from the messenger before the call to lookupNetwork completes', () => { - it('does not throw an error', async () => { - const infuraProjectId = 'some-infura-project-id'; + ]); + const fakeNetworkClient = buildFakeClient(fakeProvider); + createNetworkClientMock.mockReturnValue(fakeNetworkClient); + await controller.initializeProvider(); - await withController( - { - state: { - selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', - networkConfigurationsByChainId: { - '0x1337': buildCustomNetworkConfiguration({ - chainId: '0x1337', - rpcEndpoints: [ - buildCustomRpcEndpoint({ - networkClientId: 'AAAA-AAAA-AAAA-AAAA', - }), - ], - }), - }, + const lookupNetworkPromise = controller.lookupNetwork(); + messenger.clearSubscriptions(); + expect(await lookupNetworkPromise).toBeUndefined(); }, - infuraProjectId, - }, - async ({ controller, messenger }) => { - const fakeProvider = buildFakeProvider([ - // Called during provider initialization - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - // Called via `lookupNetwork` directly - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - createNetworkClientMock.mockReturnValue(fakeNetworkClient); - await controller.initializeProvider(); - - const lookupNetworkPromise = controller.lookupNetwork(); - messenger.clearSubscriptions(); - expect(await lookupNetworkPromise).toBeUndefined(); - }, - ); + ); + }); }); - }); - describe('if removing the networkDidChange subscription fails for an unknown reason', () => { - it('re-throws the error', async () => { - const infuraProjectId = 'some-infura-project-id'; + describe('if removing the networkDidChange subscription fails for an unknown reason', () => { + it('re-throws the error', async () => { + const infuraProjectId = 'some-infura-project-id'; - await withController( - { - state: { - selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', - networkConfigurationsByChainId: { - '0x1337': buildCustomNetworkConfiguration({ - chainId: '0x1337', - rpcEndpoints: [ - buildCustomRpcEndpoint({ - networkClientId: 'AAAA-AAAA-AAAA-AAAA', - }), - ], - }), + await withController( + { + state: { + selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', + networkConfigurationsByChainId: { + '0x1337': buildCustomNetworkConfiguration({ + chainId: '0x1337', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + }), + ], + }), + }, }, + infuraProjectId, }, - infuraProjectId, - }, - async ({ controller, messenger }) => { - const fakeProvider = buildFakeProvider([ - // Called during provider initialization - { - request: { - method: 'eth_getBlockByNumber', + async ({ controller, messenger }) => { + const fakeProvider = buildFakeProvider([ + // Called during provider initialization + { + request: { + method: 'eth_getBlockByNumber', + }, + response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - // Called via `lookupNetwork` directly - { - request: { - method: 'eth_getBlockByNumber', + // Called via `lookupNetwork` directly + { + request: { + method: 'eth_getBlockByNumber', + }, + response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - createNetworkClientMock.mockReturnValue(fakeNetworkClient); - await controller.initializeProvider(); + ]); + const fakeNetworkClient = buildFakeClient(fakeProvider); + createNetworkClientMock.mockReturnValue(fakeNetworkClient); + await controller.initializeProvider(); - const lookupNetworkPromise = controller.lookupNetwork(); - const error = new Error('oops'); - jest - .spyOn(messenger, 'unsubscribe') - .mockImplementation((eventType) => { - // This is okay. - // eslint-disable-next-line jest/no-conditional-in-test - if (eventType === 'NetworkController:networkDidChange') { - throw error; - } - }); - await expect(lookupNetworkPromise).rejects.toThrow(error); - }, - ); + const lookupNetworkPromise = controller.lookupNetwork(); + const error = new Error('oops'); + jest + .spyOn(messenger, 'unsubscribe') + .mockImplementation((eventType) => { + // This is okay. + // eslint-disable-next-line jest/no-conditional-in-test + if (eventType === 'NetworkController:networkDidChange') { + throw error; + } + }); + await expect(lookupNetworkPromise).rejects.toThrow(error); + }, + ); + }); }); - }); - lookupNetworkTests({ - expectedNetworkClientType: NetworkClientType.Custom, - initialState: { - selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', - networkConfigurationsByChainId: { - '0x1337': buildCustomNetworkConfiguration({ - chainId: '0x1337', - nativeCurrency: 'TEST', - rpcEndpoints: [ - buildCustomRpcEndpoint({ - networkClientId: 'AAAA-AAAA-AAAA-AAAA', - url: 'https://test.network', - }), - ], - }), + lookupNetworkTests({ + expectedNetworkClientType: NetworkClientType.Custom, + expectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', + initialState: { + selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', + networkConfigurationsByChainId: { + '0x1337': buildCustomNetworkConfiguration({ + chainId: '0x1337', + nativeCurrency: 'TEST', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + url: 'https://test.network', + }), + ], + }), + }, }, - }, - operation: async (controller) => { - await controller.lookupNetwork(); - }, + operation: async (controller) => { + await controller.lookupNetwork(); + }, + }); }); }); }); @@ -2609,6 +2647,7 @@ describe('NetworkController', () => { refreshNetworkTests({ expectedNetworkClientConfiguration: buildInfuraNetworkClientConfiguration(infuraNetworkType), + expectedNetworkClientId: infuraNetworkType, operation: async (controller) => { await controller.setProviderType(infuraNetworkType); }, @@ -2764,6 +2803,7 @@ describe('NetworkController', () => { refreshNetworkTests({ expectedNetworkClientConfiguration: buildInfuraNetworkClientConfiguration(infuraNetworkType), + expectedNetworkClientId: infuraNetworkType, initialState: { selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', networkConfigurationsByChainId: { @@ -2808,6 +2848,7 @@ describe('NetworkController', () => { chainId: '0x1337', ticker: 'TEST', }), + expectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', initialState: { selectedNetworkClientId: InfuraNetworkType.mainnet, networkConfigurationsByChainId: { @@ -3284,6 +3325,7 @@ describe('NetworkController', () => { refreshNetworkTests({ expectedNetworkClientConfiguration: buildInfuraNetworkClientConfiguration(infuraNetworkType), + expectedNetworkClientId: infuraNetworkType, initialState: { selectedNetworkClientId: infuraNetworkType, }, @@ -3302,6 +3344,7 @@ describe('NetworkController', () => { chainId: '0x1337', ticker: 'TEST', }), + expectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', initialState: { selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', networkConfigurationsByChainId: { @@ -12828,6 +12871,7 @@ describe('NetworkController', () => { refreshNetworkTests({ expectedNetworkClientConfiguration: buildInfuraNetworkClientConfiguration(infuraNetworkType), + expectedNetworkClientId: infuraNetworkType, initialState: { selectedNetworkClientId: infuraNetworkType, }, @@ -12846,6 +12890,7 @@ describe('NetworkController', () => { chainId: '0x1337', ticker: 'TEST', }), + expectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', initialState: { selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', networkConfigurationsByChainId: { @@ -14106,6 +14151,33 @@ describe('NetworkController', () => { ); }); }); + + describe('getSelectedNetworkClient', () => { + it('returns the selected network provider and blockTracker proxy when initialized', async () => { + await withController(async ({ controller }) => { + const fakeProvider = buildFakeProvider(); + const fakeNetworkClient = buildFakeClient(fakeProvider); + mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); + await controller.initializeProvider(); + const defaultNetworkClient = controller.getProviderAndBlockTracker(); + + const selectedNetworkClient = controller.getSelectedNetworkClient(); + expect(defaultNetworkClient.provider).toBe( + selectedNetworkClient?.provider, + ); + expect(defaultNetworkClient.blockTracker).toBe( + selectedNetworkClient?.blockTracker, + ); + }); + }); + + it('returns undefined when the selected network provider and blockTracker proxy are not initialized', async () => { + await withController(async ({ controller }) => { + const selectedNetworkClient = controller.getSelectedNetworkClient(); + expect(selectedNetworkClient).toBeUndefined(); + }); + }); + }); }); describe('getNetworkConfigurations', () => { @@ -14223,15 +14295,19 @@ function mockCreateNetworkClient() { * @param args - Arguments. * @param args.expectedNetworkClientConfiguration - The network client * configuration that the operation is expected to set. + * @param args.expectedNetworkClientId - The ID of the network client that the + * operation is expected to involve. * @param args.initialState - The initial state of the network controller. * @param args.operation - The operation to test. */ function refreshNetworkTests({ expectedNetworkClientConfiguration, + expectedNetworkClientId, initialState, operation, }: { expectedNetworkClientConfiguration: NetworkClientConfiguration; + expectedNetworkClientId: NetworkClientId; initialState?: Partial; operation: (controller: NetworkController) => Promise; }) { @@ -14479,6 +14555,7 @@ function refreshNetworkTests({ lookupNetworkTests({ expectedNetworkClientType: expectedNetworkClientConfiguration.type, + expectedNetworkClientId, initialState, operation, }); @@ -14491,22 +14568,31 @@ function refreshNetworkTests({ * * @param args - Arguments. * @param args.expectedNetworkClientType - The type of the network client - * that the operation is expected to set. + * that the operation is expected to involve. + * @param args.expectedNetworkClientId - The ID of the network client that the + * operation is expected to involve. * @param args.initialState - The initial state of the network controller. * @param args.operation - The operation to test. + * @param args.shouldTestInfuraMessengerEvents - Whether to test whether + * Infura-related messenger events are published. This is useful when the + * operation involves the currently selected network. */ function lookupNetworkTests({ expectedNetworkClientType, + expectedNetworkClientId, initialState, operation, + shouldTestInfuraMessengerEvents = true, }: { expectedNetworkClientType: NetworkClientType; + expectedNetworkClientId: NetworkClientId; initialState?: Partial; operation: (controller: NetworkController) => Promise; + shouldTestInfuraMessengerEvents?: boolean; }) { - describe('if the network details request resolve successfully', () => { - 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 () => { + describe('if the network details request resolves successfully', () => { + describe('if the new network details of the target network are different from the ones in state', () => { + it('updates state to match', async () => { await withController( { state: { @@ -14540,17 +14626,16 @@ function lookupNetworkTests({ await operation(controller); expect( - controller.state.networksMetadata[ - controller.state.selectedNetworkClientId - ].EIPS[1559], + controller.state.networksMetadata[expectedNetworkClientId] + .EIPS[1559], ).toBe(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 () => { + describe('if the new network details of the target network are the same as the ones in state', () => { + it('does not update state', async () => { await withController( { state: { @@ -14584,64 +14669,65 @@ function lookupNetworkTests({ await operation(controller); expect( - controller.state.networksMetadata[ - controller.state.selectedNetworkClientId - ].EIPS[1559], + controller.state.networksMetadata[expectedNetworkClientId] + .EIPS[1559], ).toBe(true); }, ); }); }); - it('emits infuraIsUnblocked', async () => { - await withController( - { - state: initialState, - }, - async ({ controller, messenger }) => { - await setFakeProvider(controller, { - stubLookupNetworkWhileSetting: true, - }); + if (shouldTestInfuraMessengerEvents) { + it('emits infuraIsUnblocked', async () => { + await withController( + { + state: initialState, + }, + async ({ controller, messenger }) => { + await setFakeProvider(controller, { + stubLookupNetworkWhileSetting: true, + }); - const infuraIsUnblocked = waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsUnblocked', - operation: async () => { - await operation(controller); - }, - }); + const infuraIsUnblocked = waitForPublishedEvents({ + messenger, + eventType: 'NetworkController:infuraIsUnblocked', + operation: async () => { + await operation(controller); + }, + }); - await expect(infuraIsUnblocked).toBeFulfilled(); - }, - ); - }); + await expect(infuraIsUnblocked).toBeFulfilled(); + }, + ); + }); - it('does not emit infuraIsBlocked', async () => { - await withController( - { - state: initialState, - }, - async ({ controller, messenger }) => { - await setFakeProvider(controller, { - stubLookupNetworkWhileSetting: true, - }); + it('does not emit infuraIsBlocked', async () => { + await withController( + { + state: initialState, + }, + async ({ controller, messenger }) => { + await setFakeProvider(controller, { + stubLookupNetworkWhileSetting: true, + }); - const infuraIsBlocked = waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsBlocked', - count: 0, - operation: async () => { - await operation(controller); - }, - }); + const infuraIsBlocked = waitForPublishedEvents({ + messenger, + eventType: 'NetworkController:infuraIsBlocked', + count: 0, + operation: async () => { + await operation(controller); + }, + }); - await expect(infuraIsBlocked).toBeFulfilled(); - }, - ); - }); + await expect(infuraIsBlocked).toBeFulfilled(); + }, + ); + }); + } }); - describe('if an RPC error is encountered while retrieving the network details of the current network', () => { + describe('if the network details request produces a JSON-RPC error that is not internal and not a country blocked error', () => { it('updates the network in state to "unavailable"', async () => { await withController( { @@ -14664,9 +14750,7 @@ function lookupNetworkTests({ await operation(controller); expect( - controller.state.networksMetadata[ - controller.state.selectedNetworkClientId - ].status, + controller.state.networksMetadata[expectedNetworkClientId].status, ).toBe(NetworkStatus.Unavailable); }, ); @@ -14709,48 +14793,81 @@ function lookupNetworkTests({ await operation(controller); expect( - controller.state.networksMetadata[ - controller.state.selectedNetworkClientId - ].EIPS, + controller.state.networksMetadata[expectedNetworkClientId].EIPS, ).toStrictEqual({}); }, ); }); - if (expectedNetworkClientType === NetworkClientType.Custom) { - it('emits infuraIsUnblocked', async () => { - await withController( - { - state: initialState, - }, - async ({ controller, messenger }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], + if (shouldTestInfuraMessengerEvents) { + if (expectedNetworkClientType === NetworkClientType.Custom) { + it('emits infuraIsUnblocked', async () => { + await withController( + { + state: initialState, + }, + async ({ controller, messenger }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { + method: 'eth_getBlockByNumber', + params: ['latest', false], + }, + error: rpcErrors.limitExceeded('some error'), + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + const infuraIsUnblocked = waitForPublishedEvents({ + messenger, + eventType: 'NetworkController:infuraIsUnblocked', + operation: async () => { + await operation(controller); + }, + }); + + await expect(infuraIsUnblocked).toBeFulfilled(); + }, + ); + }); + } 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: rpcErrors.limitExceeded('some error'), }, - error: rpcErrors.limitExceeded('some error'), + ], + stubLookupNetworkWhileSetting: true, + }); + + const infuraIsUnblocked = waitForPublishedEvents({ + messenger, + eventType: 'NetworkController:infuraIsUnblocked', + count: 0, + operation: async () => { + await operation(controller); }, - ], - stubLookupNetworkWhileSetting: true, - }); + }); - const infuraIsUnblocked = waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsUnblocked', - operation: async () => { - await operation(controller); - }, - }); + await expect(infuraIsUnblocked).toBeFulfilled(); + }, + ); + }); + } - await expect(infuraIsUnblocked).toBeFulfilled(); - }, - ); - }); - } else { - it('does not emit infuraIsUnblocked', async () => { + it('does not emit infuraIsBlocked', async () => { await withController( { state: initialState, @@ -14769,56 +14886,23 @@ function lookupNetworkTests({ stubLookupNetworkWhileSetting: true, }); - const infuraIsUnblocked = waitForPublishedEvents({ + const infuraIsBlocked = waitForPublishedEvents({ messenger, - eventType: 'NetworkController:infuraIsUnblocked', + eventType: 'NetworkController:infuraIsBlocked', count: 0, operation: async () => { await operation(controller); }, }); - await expect(infuraIsUnblocked).toBeFulfilled(); + await expect(infuraIsBlocked).toBeFulfilled(); }, ); }); } - - 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: rpcErrors.limitExceeded('some error'), - }, - ], - stubLookupNetworkWhileSetting: true, - }); - - const infuraIsBlocked = waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsBlocked', - count: 0, - operation: async () => { - await operation(controller); - }, - }); - - await expect(infuraIsBlocked).toBeFulfilled(); - }, - ); - }); }); - describe('if a country blocked error is encountered while retrieving the network details of the current network', () => { + describe('if the network details request produces a country blocked error', () => { if (expectedNetworkClientType === NetworkClientType.Custom) { it('updates the network in state to "unknown"', async () => { await withController( @@ -14842,78 +14926,78 @@ function lookupNetworkTests({ await operation(controller); expect( - controller.state.networksMetadata[ - controller.state.selectedNetworkClientId - ].status, + controller.state.networksMetadata[expectedNetworkClientId].status, ).toBe(NetworkStatus.Unknown); }, ); }); - it('emits infuraIsUnblocked', async () => { - await withController( - { - state: initialState, - }, - async ({ controller, messenger }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], + if (shouldTestInfuraMessengerEvents) { + 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, }, - error: BLOCKED_INFURA_JSON_RPC_ERROR, - }, - ], - stubLookupNetworkWhileSetting: true, - }); + ], + stubLookupNetworkWhileSetting: true, + }); - const infuraIsUnblocked = waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsUnblocked', - operation: async () => { - await operation(controller); - }, - }); + const infuraIsUnblocked = waitForPublishedEvents({ + messenger, + eventType: 'NetworkController:infuraIsUnblocked', + operation: async () => { + await operation(controller); + }, + }); - await expect(infuraIsUnblocked).toBeFulfilled(); - }, - ); - }); + await expect(infuraIsUnblocked).toBeFulfilled(); + }, + ); + }); - it('does not emit infuraIsBlocked', async () => { - await withController( - { - state: initialState, - }, - async ({ controller, messenger }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], + 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, }, - error: BLOCKED_INFURA_JSON_RPC_ERROR, - }, - ], - stubLookupNetworkWhileSetting: true, - }); + ], + stubLookupNetworkWhileSetting: true, + }); - const infuraIsBlocked = waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsBlocked', - count: 0, - operation: async () => { - await operation(controller); - }, - }); + const infuraIsBlocked = waitForPublishedEvents({ + messenger, + eventType: 'NetworkController:infuraIsBlocked', + count: 0, + operation: async () => { + await operation(controller); + }, + }); - await expect(infuraIsBlocked).toBeFulfilled(); - }, - ); - }); + await expect(infuraIsBlocked).toBeFulfilled(); + }, + ); + }); + } } else { it('updates the network in state to "blocked"', async () => { await withController( @@ -14937,78 +15021,78 @@ function lookupNetworkTests({ await operation(controller); expect( - controller.state.networksMetadata[ - controller.state.selectedNetworkClientId - ].status, + controller.state.networksMetadata[expectedNetworkClientId].status, ).toBe(NetworkStatus.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], + if (shouldTestInfuraMessengerEvents) { + 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, }, - error: BLOCKED_INFURA_JSON_RPC_ERROR, - }, - ], - stubLookupNetworkWhileSetting: true, - }); + ], + stubLookupNetworkWhileSetting: true, + }); - const infuraIsUnblocked = waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsUnblocked', - count: 0, - operation: async () => { - await operation(controller); - }, - }); + const infuraIsUnblocked = waitForPublishedEvents({ + messenger, + eventType: 'NetworkController:infuraIsUnblocked', + count: 0, + operation: async () => { + await operation(controller); + }, + }); - await expect(infuraIsUnblocked).toBeFulfilled(); - }, - ); - }); + await expect(infuraIsUnblocked).toBeFulfilled(); + }, + ); + }); - it('emits infuraIsBlocked', async () => { - await withController( - { - state: initialState, - }, - async ({ controller, messenger }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], + 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, }, - error: BLOCKED_INFURA_JSON_RPC_ERROR, - }, - ], - stubLookupNetworkWhileSetting: true, - }); + ], + stubLookupNetworkWhileSetting: true, + }); - const infuraIsBlocked = waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsBlocked', - operation: async () => { - await operation(controller); - }, - }); + const infuraIsBlocked = waitForPublishedEvents({ + messenger, + eventType: 'NetworkController:infuraIsBlocked', + operation: async () => { + await operation(controller); + }, + }); - await expect(infuraIsBlocked).toBeFulfilled(); - }, - ); - }); + await expect(infuraIsBlocked).toBeFulfilled(); + }, + ); + }); + } } it('resets the network details in state', async () => { @@ -15048,16 +15132,14 @@ function lookupNetworkTests({ await operation(controller); expect( - controller.state.networksMetadata[ - controller.state.selectedNetworkClientId - ].EIPS, + controller.state.networksMetadata[expectedNetworkClientId].EIPS, ).toStrictEqual({}); }, ); }); }); - describe('if an internal error is encountered while retrieving the network details of the current network', () => { + describe('if the network details request produces an internal JSON-RPC error', () => { it('updates the network in state to "unknown"', async () => { await withController( { @@ -15080,9 +15162,7 @@ function lookupNetworkTests({ await operation(controller); expect( - controller.state.networksMetadata[ - controller.state.selectedNetworkClientId - ].status, + controller.state.networksMetadata[expectedNetworkClientId].status, ).toBe(NetworkStatus.Unknown); }, ); @@ -15125,48 +15205,81 @@ function lookupNetworkTests({ await operation(controller); expect( - controller.state.networksMetadata[ - controller.state.selectedNetworkClientId - ].EIPS, + controller.state.networksMetadata[expectedNetworkClientId].EIPS, ).toStrictEqual({}); }, ); }); - if (expectedNetworkClientType === NetworkClientType.Custom) { - it('emits infuraIsUnblocked', async () => { - await withController( - { - state: initialState, - }, - async ({ controller, messenger }) => { - await setFakeProvider(controller, { - stubs: [ - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], + if (shouldTestInfuraMessengerEvents) { + if (expectedNetworkClientType === NetworkClientType.Custom) { + it('emits infuraIsUnblocked', async () => { + await withController( + { + state: initialState, + }, + async ({ controller, messenger }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { + method: 'eth_getBlockByNumber', + params: ['latest', false], + }, + error: GENERIC_JSON_RPC_ERROR, }, - error: GENERIC_JSON_RPC_ERROR, + ], + stubLookupNetworkWhileSetting: true, + }); + + const infuraIsUnblocked = waitForPublishedEvents({ + messenger, + eventType: 'NetworkController:infuraIsUnblocked', + operation: async () => { + await operation(controller); }, - ], - stubLookupNetworkWhileSetting: true, - }); + }); - const infuraIsUnblocked = waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsUnblocked', - operation: async () => { - await operation(controller); - }, - }); + await expect(infuraIsUnblocked).toBeFulfilled(); + }, + ); + }); + } 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: GENERIC_JSON_RPC_ERROR, + }, + ], + stubLookupNetworkWhileSetting: true, + }); - await expect(infuraIsUnblocked).toBeFulfilled(); - }, - ); - }); - } else { - it('does not emit infuraIsUnblocked', async () => { + const infuraIsUnblocked = waitForPublishedEvents({ + messenger, + eventType: 'NetworkController:infuraIsUnblocked', + count: 0, + operation: async () => { + await operation(controller); + }, + }); + + await expect(infuraIsUnblocked).toBeFulfilled(); + }, + ); + }); + } + + it('does not emit infuraIsBlocked', async () => { await withController( { state: initialState, @@ -15185,27 +15298,29 @@ function lookupNetworkTests({ stubLookupNetworkWhileSetting: true, }); - const infuraIsUnblocked = waitForPublishedEvents({ + const infuraIsBlocked = waitForPublishedEvents({ messenger, - eventType: 'NetworkController:infuraIsUnblocked', + eventType: 'NetworkController:infuraIsBlocked', count: 0, operation: async () => { await operation(controller); }, }); - await expect(infuraIsUnblocked).toBeFulfilled(); + await expect(infuraIsBlocked).toBeFulfilled(); }, ); }); } + }); - it('does not emit infuraIsBlocked', async () => { + describe('if the network details request produces a non-JSON-RPC error', () => { + it('updates the network in state to "unknown"', async () => { await withController( { state: initialState, }, - async ({ controller, messenger }) => { + async ({ controller }) => { await setFakeProvider(controller, { stubs: [ { @@ -15213,52 +15328,165 @@ function lookupNetworkTests({ method: 'eth_getBlockByNumber', params: ['latest', false], }, - error: GENERIC_JSON_RPC_ERROR, + error: 'oops', }, ], stubLookupNetworkWhileSetting: true, }); - const infuraIsBlocked = waitForPublishedEvents({ - messenger, - eventType: 'NetworkController:infuraIsBlocked', - count: 0, - operation: async () => { - await operation(controller); - }, - }); + await operation(controller); - await expect(infuraIsBlocked).toBeFulfilled(); + expect( + controller.state.networksMetadata[expectedNetworkClientId].status, + ).toBe(NetworkStatus.Unknown); }, ); }); - }); - describe('getSelectedNetworkClient', () => { - it('returns the selected network provider and blockTracker proxy when initialized', async () => { - await withController(async ({ controller }) => { - const fakeProvider = buildFakeProvider(); - const fakeNetworkClient = buildFakeClient(fakeProvider); - mockCreateNetworkClient().mockReturnValue(fakeNetworkClient); - await controller.initializeProvider(); - const defaultNetworkClient = controller.getProviderAndBlockTracker(); + 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.state.networksMetadata[ + controller.state.selectedNetworkClientId + ].EIPS[1559], + ).toBe(false); - const selectedNetworkClient = controller.getSelectedNetworkClient(); - expect(defaultNetworkClient.provider).toBe( - selectedNetworkClient?.provider, - ); - expect(defaultNetworkClient.blockTracker).toBe( - selectedNetworkClient?.blockTracker, - ); - }); + await operation(controller); + + expect( + controller.state.networksMetadata[expectedNetworkClientId].EIPS, + ).toStrictEqual({}); + }, + ); }); - it('returns undefined when the selected network provider and blockTracker proxy are not initialized', async () => { - await withController(async ({ controller }) => { - const selectedNetworkClient = controller.getSelectedNetworkClient(); - expect(selectedNetworkClient).toBeUndefined(); + if (shouldTestInfuraMessengerEvents) { + if (expectedNetworkClientType === NetworkClientType.Custom) { + it('emits infuraIsUnblocked', async () => { + await withController( + { + state: initialState, + }, + async ({ controller, messenger }) => { + await setFakeProvider(controller, { + stubs: [ + { + request: { + method: 'eth_getBlockByNumber', + params: ['latest', false], + }, + error: GENERIC_JSON_RPC_ERROR, + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + const infuraIsUnblocked = waitForPublishedEvents({ + messenger, + eventType: 'NetworkController:infuraIsUnblocked', + operation: async () => { + await operation(controller); + }, + }); + + await expect(infuraIsUnblocked).toBeFulfilled(); + }, + ); + }); + } 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: GENERIC_JSON_RPC_ERROR, + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + const infuraIsUnblocked = waitForPublishedEvents({ + messenger, + eventType: 'NetworkController:infuraIsUnblocked', + count: 0, + operation: async () => { + await operation(controller); + }, + }); + + await expect(infuraIsUnblocked).toBeFulfilled(); + }, + ); + }); + } + + 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: GENERIC_JSON_RPC_ERROR, + }, + ], + stubLookupNetworkWhileSetting: true, + }); + + const infuraIsBlocked = waitForPublishedEvents({ + messenger, + eventType: 'NetworkController:infuraIsBlocked', + count: 0, + operation: async () => { + await operation(controller); + }, + }); + + await expect(infuraIsBlocked).toBeFulfilled(); + }, + ); }); - }); + } }); } From 8a34ebd9e726f65634f61f57f74db0003ccf10ca Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Thu, 14 Aug 2025 07:53:28 -0600 Subject: [PATCH 02/10] Don't remove yet, just deprecate --- packages/network-controller/CHANGELOG.md | 6 +++--- packages/network-controller/src/NetworkController.ts | 6 ++++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/network-controller/CHANGELOG.md b/packages/network-controller/CHANGELOG.md index 3eeddabc445..d3bdc3548fd 100644 --- a/packages/network-controller/CHANGELOG.md +++ b/packages/network-controller/CHANGELOG.md @@ -7,10 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -### Removed +### Deprecated -- **BREAKING:** Remove `lookupNetworkByClientId` ([#6308](https://github.com/MetaMask/core/pull/6308)) - - `lookupNetwork` already supports passing in a network client ID; please use this instead. +- Deprecate `lookupNetworkByClientId` ([#6308](https://github.com/MetaMask/core/pull/6308)) + - `lookupNetwork` already supports passing in a network client ID; please use this going forward instead. ## [24.1.0] diff --git a/packages/network-controller/src/NetworkController.ts b/packages/network-controller/src/NetworkController.ts index 5ff37e39afd..f2810649b2c 100644 --- a/packages/network-controller/src/NetworkController.ts +++ b/packages/network-controller/src/NetworkController.ts @@ -1648,7 +1648,7 @@ export class NetworkController extends BaseController< */ async lookupNetwork(networkClientId?: NetworkClientId) { if (networkClientId) { - await this.#lookupGivenNetwork(networkClientId); + await this.lookupNetworkByNetworkClientId(networkClientId); } else { await this.#lookupSelectedNetwork(); } @@ -1664,8 +1664,10 @@ export class NetworkController extends BaseController< * whether it does not, or whether it is unknown * * @param networkClientId - The ID of the network client to inspect. + * @deprecated Please use `lookupNetwork` and pass a network client ID + * instead. This method will be removed in a future major version. */ - async #lookupGivenNetwork(networkClientId: NetworkClientId) { + async lookupNetworkByNetworkClientId(networkClientId: NetworkClientId) { const { networkStatus, isEIP1559Compatible } = await this.#determineNetworkMetadata(networkClientId); this.#updateMetadataForNetwork( From aa83d3bd137b6fd2877d99afb9e556ea7dd429b2 Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Mon, 25 Aug 2025 11:18:23 -0600 Subject: [PATCH 03/10] Use private method to make future refactoring easier --- .../network-controller/src/NetworkController.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/packages/network-controller/src/NetworkController.ts b/packages/network-controller/src/NetworkController.ts index f2810649b2c..548809e1496 100644 --- a/packages/network-controller/src/NetworkController.ts +++ b/packages/network-controller/src/NetworkController.ts @@ -1648,7 +1648,7 @@ export class NetworkController extends BaseController< */ async lookupNetwork(networkClientId?: NetworkClientId) { if (networkClientId) { - await this.lookupNetworkByNetworkClientId(networkClientId); + await this.#lookupGivenNetwork(networkClientId); } else { await this.#lookupSelectedNetwork(); } @@ -1668,6 +1668,21 @@ export class NetworkController extends BaseController< * instead. This method will be removed in a future major version. */ async lookupNetworkByNetworkClientId(networkClientId: NetworkClientId) { + await this.#lookupGivenNetwork(networkClientId); + } + + /** + * Uses a request for the latest block to gather the following information on + * the given network, persisting it to state: + * + * - The connectivity status: whether the network is available, geo-blocked + * (Infura only), unavailable, or unknown + * - The feature compatibility status: whether the network supports EIP-1559, + * whether it does not, or whether it is unknown + * + * @param networkClientId - The ID of the network client to inspect. + */ + async #lookupGivenNetwork(networkClientId: NetworkClientId) { const { networkStatus, isEIP1559Compatible } = await this.#determineNetworkMetadata(networkClientId); this.#updateMetadataForNetwork( From db9f81cc5a9acde039ba63dc5be965ed4841498e Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Mon, 25 Aug 2025 11:54:21 -0600 Subject: [PATCH 04/10] Add missing tests --- .../src/NetworkController.ts | 7 + .../tests/NetworkController.test.ts | 172 ++++++++++++++++++ 2 files changed, 179 insertions(+) diff --git a/packages/network-controller/src/NetworkController.ts b/packages/network-controller/src/NetworkController.ts index 548809e1496..2449906ffe5 100644 --- a/packages/network-controller/src/NetworkController.ts +++ b/packages/network-controller/src/NetworkController.ts @@ -1667,6 +1667,9 @@ export class NetworkController extends BaseController< * @deprecated Please use `lookupNetwork` and pass a network client ID * instead. This method will be removed in a future major version. */ + // We are planning on removing this so we aren't interested in testing this + // right now. + /* istanbul ignore next */ async lookupNetworkByNetworkClientId(networkClientId: NetworkClientId) { await this.#lookupGivenNetwork(networkClientId); } @@ -1706,6 +1709,10 @@ export class NetworkController extends BaseController< * will also run for the new network). */ async #lookupSelectedNetwork() { + if (!this.#ethQuery) { + return; + } + let networkChanged = false; const listener = () => { networkChanged = true; diff --git a/packages/network-controller/tests/NetworkController.test.ts b/packages/network-controller/tests/NetworkController.test.ts index 70e40549d58..1d54513d0fd 100644 --- a/packages/network-controller/tests/NetworkController.test.ts +++ b/packages/network-controller/tests/NetworkController.test.ts @@ -1813,6 +1813,83 @@ describe('NetworkController', () => { const infuraChainId = ChainId[infuraNetworkType]; describe(`when the selected network client represents the Infura network "${infuraNetworkType}"`, () => { + describe('if the provider has been not been initialized yet', () => { + it('does not update state', async () => { + await withController( + { + state: { + selectedNetworkClientId: infuraNetworkType, + networkConfigurationsByChainId: { + [infuraChainId]: + buildInfuraNetworkConfiguration(infuraNetworkType), + }, + }, + }, + async ({ controller, messenger }) => { + const stateChangeListener = jest.fn(); + messenger.subscribe( + 'NetworkController:stateChange', + stateChangeListener, + ); + + await controller.lookupNetwork(); + + expect(stateChangeListener).not.toHaveBeenCalled(); + }, + ); + }); + + it('does not publish NetworkController:infuraIsUnblocked', async () => { + await withController( + { + state: { + selectedNetworkClientId: infuraNetworkType, + networkConfigurationsByChainId: { + [infuraChainId]: + buildInfuraNetworkConfiguration(infuraNetworkType), + }, + }, + }, + async ({ controller, messenger }) => { + const infuraIsUnblockedListener = jest.fn(); + messenger.subscribe( + 'NetworkController:infuraIsUnblocked', + infuraIsUnblockedListener, + ); + + await controller.lookupNetwork(); + + expect(infuraIsUnblockedListener).not.toHaveBeenCalled(); + }, + ); + }); + + it('does not publish NetworkController:infuraIsBlocked', async () => { + await withController( + { + state: { + selectedNetworkClientId: infuraNetworkType, + networkConfigurationsByChainId: { + [infuraChainId]: + buildInfuraNetworkConfiguration(infuraNetworkType), + }, + }, + }, + async ({ controller, messenger }) => { + const infuraIsBlockedListener = jest.fn(); + messenger.subscribe( + 'NetworkController:infuraIsBlocked', + infuraIsBlockedListener, + ); + + await controller.lookupNetwork(); + + expect(infuraIsBlockedListener).not.toHaveBeenCalled(); + }, + ); + }); + }); + 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 () => { const infuraProjectId = 'some-infura-project-id'; @@ -2211,6 +2288,101 @@ describe('NetworkController', () => { } describe('when the selected network client represents a custom RPC endpoint', () => { + describe('if the provider has been not been initialized yet', () => { + it('does not update state', async () => { + await withController( + { + state: { + selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', + networkConfigurationsByChainId: { + '0x1337': buildCustomNetworkConfiguration({ + chainId: '0x1337', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + }), + ], + }), + }, + }, + }, + async ({ controller, messenger }) => { + const stateChangeListener = jest.fn(); + messenger.subscribe( + 'NetworkController:stateChange', + stateChangeListener, + ); + + await controller.lookupNetwork(); + + expect(stateChangeListener).not.toHaveBeenCalled(); + }, + ); + }); + + it('does not publish NetworkController:infuraIsUnblocked', async () => { + await withController( + { + state: { + selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', + networkConfigurationsByChainId: { + '0x1337': buildCustomNetworkConfiguration({ + chainId: '0x1337', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + }), + ], + }), + }, + }, + }, + async ({ controller, messenger }) => { + const infuraIsUnblockedListener = jest.fn(); + messenger.subscribe( + 'NetworkController:infuraIsUnblocked', + infuraIsUnblockedListener, + ); + + await controller.lookupNetwork(); + + expect(infuraIsUnblockedListener).not.toHaveBeenCalled(); + }, + ); + }); + + it('does not publish NetworkController:infuraIsBlocked', async () => { + await withController( + { + state: { + selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', + networkConfigurationsByChainId: { + '0x1337': buildCustomNetworkConfiguration({ + chainId: '0x1337', + rpcEndpoints: [ + buildCustomRpcEndpoint({ + networkClientId: 'AAAA-AAAA-AAAA-AAAA', + }), + ], + }), + }, + }, + }, + async ({ controller, messenger }) => { + const infuraIsBlockedListener = jest.fn(); + messenger.subscribe( + 'NetworkController:infuraIsBlocked', + infuraIsBlockedListener, + ); + + await controller.lookupNetwork(); + + expect(infuraIsBlockedListener).not.toHaveBeenCalled(); + }, + ); + }); + }); + 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 () => { const infuraProjectId = 'some-infura-project-id'; From 16768efb14cd5cb396d0993bc6f4fbc26247cafb Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Mon, 25 Aug 2025 13:07:49 -0600 Subject: [PATCH 05/10] Fix changelog --- packages/network-controller/CHANGELOG.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/network-controller/CHANGELOG.md b/packages/network-controller/CHANGELOG.md index 041f74f3454..a27b90aa487 100644 --- a/packages/network-controller/CHANGELOG.md +++ b/packages/network-controller/CHANGELOG.md @@ -7,15 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- Bump `@metamask/base-controller` from `^8.1.0` to `^8.2.0` ([#6355](https://github.com/MetaMask/core/pull/6355)) + ### Deprecated - Deprecate `lookupNetworkByClientId` ([#6308](https://github.com/MetaMask/core/pull/6308)) - `lookupNetwork` already supports passing in a network client ID; please use this going forward instead. -### Changed - -- Bump `@metamask/base-controller` from `^8.1.0` to `^8.2.0` ([#6355](https://github.com/MetaMask/core/pull/6355)) - ## [24.1.0] ### Added From 4c0c12eef341fff42f6522af3fe887051c8982a5 Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Tue, 26 Aug 2025 15:06:16 -0600 Subject: [PATCH 06/10] lookupNetworkByNetworkClientId -> lookupNetworkByNetworkClientId --- packages/network-controller/src/NetworkController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/network-controller/src/NetworkController.ts b/packages/network-controller/src/NetworkController.ts index 2449906ffe5..277a0b7915d 100644 --- a/packages/network-controller/src/NetworkController.ts +++ b/packages/network-controller/src/NetworkController.ts @@ -1670,7 +1670,7 @@ export class NetworkController extends BaseController< // We are planning on removing this so we aren't interested in testing this // right now. /* istanbul ignore next */ - async lookupNetworkByNetworkClientId(networkClientId: NetworkClientId) { + async lookupNetworkByClientId(networkClientId: NetworkClientId) { await this.#lookupGivenNetwork(networkClientId); } From 96b80f04d78fb99fc6d3012c317a800e28ac24b9 Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Tue, 26 Aug 2025 15:07:44 -0600 Subject: [PATCH 07/10] chore(network-controller): Simplify network switch detection in lookupNetwork `lookupNetwork` operates on an arbitrary network or, if given nothing, the globally selected network. The behavior between these two cases is largely the same, except that in the "selected network" case, it publishes one of two events, `NetworkController:infuraIsBlocked` or `NetworkController:infuraIsUnblocked`. This happens after making a request to the network, and while the request is running, it is possible for the network to change. This creates a potential problem because if that happens, the events that we want to fire would no longer represent the network they were intended to represent. `lookupNetwork` accounts for this by subscribing to `NetworkController:networkDidChange` before starting the request; if it's fired, then it exits early (with the presumption that the network switch called its own version of `lookupNetwork` and will publish `NetworkController:infuraIsBlocked` or `NetworkController:infuraIsUnblocked` at that point in time). However, this approach to detect a network switch is complicated and unnecessary. We can simply capture `selectedNetworkClientId` before we start the request, ask what it is afterward, and compare the two to know whether the network switched. This means that we can bring the implementation of the two branches within `lookupNetwork` closer together (and we can eliminate some tests). --- .../src/NetworkController.ts | 143 +++++------------- .../tests/NetworkController.test.ts | 108 ------------- 2 files changed, 35 insertions(+), 216 deletions(-) diff --git a/packages/network-controller/src/NetworkController.ts b/packages/network-controller/src/NetworkController.ts index 277a0b7915d..73dd859b03e 100644 --- a/packages/network-controller/src/NetworkController.ts +++ b/packages/network-controller/src/NetworkController.ts @@ -1558,8 +1558,8 @@ export class NetworkController extends BaseController< } /** - * Uses a request for the latest block to gather the following information on - * the given network: + * Uses a request for the latest block to gather and record the following + * information on the given network: * * - The connectivity status: whether it is available, geo-blocked (Infura * only), unavailable, or unknown @@ -1570,7 +1570,7 @@ export class NetworkController extends BaseController< * If no ID is provided, uses the currently selected network. * @returns The resulting metadata for the network. */ - async #determineNetworkMetadata(networkClientId: NetworkClientId) { + async #captureNetworkMetadata(networkClientId: NetworkClientId) { // Force TypeScript to use one of the two overloads explicitly const networkClient = isInfuraNetworkType(networkClientId) ? this.getNetworkClientById(networkClientId) @@ -1631,6 +1631,22 @@ export class NetworkController extends BaseController< } } + this.update((state) => { + if (state.networksMetadata[networkClientId] === undefined) { + state.networksMetadata[networkClientId] = { + status: NetworkStatus.Unknown, + EIPS: {}, + }; + } + const meta = state.networksMetadata[networkClientId]; + meta.status = networkStatus; + if (isEIP1559Compatible === undefined) { + delete meta.EIPS[1559]; + } else { + meta.EIPS[1559] = isEIP1559Compatible; + } + }); + return { isInfura, networkStatus, isEIP1559Compatible }; } @@ -1686,13 +1702,7 @@ export class NetworkController extends BaseController< * @param networkClientId - The ID of the network client to inspect. */ async #lookupGivenNetwork(networkClientId: NetworkClientId) { - const { networkStatus, isEIP1559Compatible } = - await this.#determineNetworkMetadata(networkClientId); - this.#updateMetadataForNetwork( - networkClientId, - networkStatus, - isEIP1559Compatible, - ); + await this.#captureNetworkMetadata(networkClientId); } /** @@ -1713,113 +1723,30 @@ export class NetworkController extends BaseController< return; } - let networkChanged = false; - const listener = () => { - networkChanged = true; - try { - this.messagingSystem.unsubscribe( - 'NetworkController:networkDidChange', - listener, - ); - } catch (error) { - // In theory, this `catch` should not be necessary given that this error - // would occur "inside" of the call to `#determineEIP1559Compatibility` - // below and so it should be caught by the `try`/`catch` below (it is - // impossible to reproduce in tests for that reason). However, somehow - // it occurs within Mobile and so we have to add our own `try`/`catch` - // here. - /* istanbul ignore next */ - if ( - !(error instanceof Error) || - error.message !== - 'Subscription not found for event: NetworkController:networkDidChange' - ) { - // Again, this error should not happen and is impossible to reproduce - // in tests. - /* istanbul ignore next */ - throw error; - } - } - }; - this.messagingSystem.subscribe( - 'NetworkController:networkDidChange', - listener, - ); - - const { isInfura, networkStatus, isEIP1559Compatible } = - await this.#determineNetworkMetadata(this.state.selectedNetworkClientId); - - 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; - } + // Capture up front in case the network is switched while awaiting the + // network request + const { selectedNetworkClientId } = this.state; - try { - this.messagingSystem.unsubscribe( - 'NetworkController:networkDidChange', - listener, - ); - } catch (error) { - if ( - !(error instanceof Error) || - error.message !== - 'Subscription not found for event: NetworkController:networkDidChange' - ) { - throw error; - } - } - - this.#updateMetadataForNetwork( + const { isInfura, networkStatus } = await this.#captureNetworkMetadata( this.state.selectedNetworkClientId, - networkStatus, - isEIP1559Compatible, ); - if (isInfura) { - if (networkStatus === NetworkStatus.Available) { + if (selectedNetworkClientId === this.state.selectedNetworkClientId) { + if (isInfura) { + if (networkStatus === NetworkStatus.Available) { + this.messagingSystem.publish('NetworkController:infuraIsUnblocked'); + } else if (networkStatus === NetworkStatus.Blocked) { + this.messagingSystem.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.messagingSystem.publish('NetworkController:infuraIsUnblocked'); - } else if (networkStatus === NetworkStatus.Blocked) { - this.messagingSystem.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.messagingSystem.publish('NetworkController:infuraIsUnblocked'); } } - /** - * Updates the metadata for the given network in state. - * - * @param networkClientId - The associated network client ID. - * @param networkStatus - The network status to store in state. - * @param isEIP1559Compatible - The EIP-1559 compatibility status to - * store in state. - */ - #updateMetadataForNetwork( - networkClientId: NetworkClientId, - networkStatus: NetworkStatus, - isEIP1559Compatible: boolean | undefined, - ) { - this.update((state) => { - if (state.networksMetadata[networkClientId] === undefined) { - state.networksMetadata[networkClientId] = { - status: NetworkStatus.Unknown, - EIPS: {}, - }; - } - const meta = state.networksMetadata[networkClientId]; - meta.status = networkStatus; - if (isEIP1559Compatible === undefined) { - delete meta.EIPS[1559]; - } else { - meta.EIPS[1559] = isEIP1559Compatible; - } - }); - } - /** * Convenience method to update provider network type settings. * diff --git a/packages/network-controller/tests/NetworkController.test.ts b/packages/network-controller/tests/NetworkController.test.ts index 1d54513d0fd..e9cbb6d248f 100644 --- a/packages/network-controller/tests/NetworkController.test.ts +++ b/packages/network-controller/tests/NetworkController.test.ts @@ -2225,55 +2225,6 @@ describe('NetworkController', () => { }); }); - describe('if removing the networkDidChange subscription fails for an unknown reason', () => { - it('re-throws the error', async () => { - const infuraProjectId = 'some-infura-project-id'; - - await withController( - { - state: { - selectedNetworkClientId: infuraNetworkType, - }, - infuraProjectId, - }, - async ({ controller, messenger }) => { - const fakeProvider = buildFakeProvider([ - // Called during provider initialization - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - // Called via `lookupNetwork` directly - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - createNetworkClientMock.mockReturnValue(fakeNetworkClient); - await controller.initializeProvider(); - - const lookupNetworkPromise = controller.lookupNetwork(); - const error = new Error('oops'); - jest - .spyOn(messenger, 'unsubscribe') - .mockImplementation((eventType) => { - // This is okay. - // eslint-disable-next-line jest/no-conditional-in-test - if (eventType === 'NetworkController:networkDidChange') { - throw error; - } - }); - await expect(lookupNetworkPromise).rejects.toThrow(error); - }, - ); - }); - }); - lookupNetworkTests({ expectedNetworkClientType: NetworkClientType.Infura, expectedNetworkClientId: infuraNetworkType, @@ -2726,65 +2677,6 @@ describe('NetworkController', () => { }); }); - describe('if removing the networkDidChange subscription fails for an unknown reason', () => { - it('re-throws the error', async () => { - const infuraProjectId = 'some-infura-project-id'; - - await withController( - { - state: { - selectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', - networkConfigurationsByChainId: { - '0x1337': buildCustomNetworkConfiguration({ - chainId: '0x1337', - rpcEndpoints: [ - buildCustomRpcEndpoint({ - networkClientId: 'AAAA-AAAA-AAAA-AAAA', - }), - ], - }), - }, - }, - infuraProjectId, - }, - async ({ controller, messenger }) => { - const fakeProvider = buildFakeProvider([ - // Called during provider initialization - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - // Called via `lookupNetwork` directly - { - request: { - method: 'eth_getBlockByNumber', - }, - response: SUCCESSFUL_ETH_GET_BLOCK_BY_NUMBER_RESPONSE, - }, - ]); - const fakeNetworkClient = buildFakeClient(fakeProvider); - createNetworkClientMock.mockReturnValue(fakeNetworkClient); - await controller.initializeProvider(); - - const lookupNetworkPromise = controller.lookupNetwork(); - const error = new Error('oops'); - jest - .spyOn(messenger, 'unsubscribe') - .mockImplementation((eventType) => { - // This is okay. - // eslint-disable-next-line jest/no-conditional-in-test - if (eventType === 'NetworkController:networkDidChange') { - throw error; - } - }); - await expect(lookupNetworkPromise).rejects.toThrow(error); - }, - ); - }); - }); - lookupNetworkTests({ expectedNetworkClientType: NetworkClientType.Custom, expectedNetworkClientId: 'AAAA-AAAA-AAAA-AAAA', From bfde4da778c530516b5a23ed0ef4cb695fbfda82 Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Fri, 5 Sep 2025 07:36:25 -0600 Subject: [PATCH 08/10] Fix changelog --- packages/network-controller/CHANGELOG.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/network-controller/CHANGELOG.md b/packages/network-controller/CHANGELOG.md index 0eb3f098516..457ae212015 100644 --- a/packages/network-controller/CHANGELOG.md +++ b/packages/network-controller/CHANGELOG.md @@ -13,11 +13,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Deprecated -- Deprecate `lookupNetworkByClientId` ([#6308](https://github.com/MetaMask/core/pull/6308)) - - `lookupNetwork` already supports passing in a network client ID; please use this going forward instead. - -### Deprecated - - Deprecate `lookupNetworkByClientId` ([#6308](https://github.com/MetaMask/core/pull/6308)) - `lookupNetwork` already supports passing in a network client ID; please use this going forward instead. From 3d275de045444e00ed842166f569f0ca28c4a44f Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Fri, 5 Sep 2025 08:40:08 -0600 Subject: [PATCH 09/10] Split up the code better --- .../src/NetworkController.ts | 105 ++++++++++++------ 1 file changed, 72 insertions(+), 33 deletions(-) diff --git a/packages/network-controller/src/NetworkController.ts b/packages/network-controller/src/NetworkController.ts index 73dd859b03e..6a6b775a4e9 100644 --- a/packages/network-controller/src/NetworkController.ts +++ b/packages/network-controller/src/NetworkController.ts @@ -1558,8 +1558,8 @@ export class NetworkController extends BaseController< } /** - * Uses a request for the latest block to gather and record the following - * information on the given network: + * Uses a request for the latest block to gather the following information on + * the given network: * * - The connectivity status: whether it is available, geo-blocked (Infura * only), unavailable, or unknown @@ -1568,9 +1568,10 @@ export class NetworkController extends BaseController< * * @param networkClientId - The ID of the network client to inspect. * If no ID is provided, uses the currently selected network. - * @returns The resulting metadata for the network. + * @returns The resulting metadata for the network, or null if the network is + * switched while the latest block is being fetched. */ - async #captureNetworkMetadata(networkClientId: NetworkClientId) { + async #determineNetworkMetadata(networkClientId: NetworkClientId) { // Force TypeScript to use one of the two overloads explicitly const networkClient = isInfuraNetworkType(networkClientId) ? this.getNetworkClientById(networkClientId) @@ -1631,22 +1632,6 @@ export class NetworkController extends BaseController< } } - this.update((state) => { - if (state.networksMetadata[networkClientId] === undefined) { - state.networksMetadata[networkClientId] = { - status: NetworkStatus.Unknown, - EIPS: {}, - }; - } - const meta = state.networksMetadata[networkClientId]; - meta.status = networkStatus; - if (isEIP1559Compatible === undefined) { - delete meta.EIPS[1559]; - } else { - meta.EIPS[1559] = isEIP1559Compatible; - } - }); - return { isInfura, networkStatus, isEIP1559Compatible }; } @@ -1702,7 +1687,13 @@ export class NetworkController extends BaseController< * @param networkClientId - The ID of the network client to inspect. */ async #lookupGivenNetwork(networkClientId: NetworkClientId) { - await this.#captureNetworkMetadata(networkClientId); + const { networkStatus, isEIP1559Compatible } = + await this.#determineNetworkMetadata(networkClientId); + this.#updateNetworkMetadata( + networkClientId, + networkStatus, + isEIP1559Compatible, + ); } /** @@ -1727,23 +1718,71 @@ export class NetworkController extends BaseController< // network request const { selectedNetworkClientId } = this.state; - const { isInfura, networkStatus } = await this.#captureNetworkMetadata( - this.state.selectedNetworkClientId, - ); + const { isInfura, networkStatus, isEIP1559Compatible } = + await this.#determineNetworkMetadata(selectedNetworkClientId); if (selectedNetworkClientId === this.state.selectedNetworkClientId) { - if (isInfura) { - if (networkStatus === NetworkStatus.Available) { - this.messagingSystem.publish('NetworkController:infuraIsUnblocked'); - } else if (networkStatus === NetworkStatus.Blocked) { - this.messagingSystem.publish('NetworkController:infuraIsBlocked'); - } + this.#updateNetworkMetadata( + selectedNetworkClientId, + networkStatus, + isEIP1559Compatible, + ); + this.#publishInfuraBlockedStatusEvents(isInfura, networkStatus); + } + } + + /** + * Updates the metadata for the given network in state. + * + * @param networkClientId - The associated network client ID. + * @param networkStatus - The network status to store in state. + * @param isEIP1559Compatible - The EIP-1559 compatibility status to + * store in state. + */ + #updateNetworkMetadata( + networkClientId: NetworkClientId, + networkStatus: NetworkStatus, + isEIP1559Compatible: boolean | undefined, + ) { + this.update((state) => { + if (state.networksMetadata[networkClientId] === undefined) { + state.networksMetadata[networkClientId] = { + status: NetworkStatus.Unknown, + EIPS: {}, + }; + } + const meta = state.networksMetadata[networkClientId]; + meta.status = networkStatus; + if (isEIP1559Compatible === undefined) { + delete meta.EIPS[1559]; } 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 + meta.EIPS[1559] = isEIP1559Compatible; + } + }); + } + + /** + * Publishes events that advise users about whether the recently updated + * network is geo-blocked by Infura. + * + * @param isInfura - Whether the network points to an Infura URL. + * @param networkStatus - The status of the network. + */ + #publishInfuraBlockedStatusEvents( + isInfura: boolean, + networkStatus: NetworkStatus, + ) { + if (isInfura) { + if (networkStatus === NetworkStatus.Available) { this.messagingSystem.publish('NetworkController:infuraIsUnblocked'); + } else if (networkStatus === NetworkStatus.Blocked) { + this.messagingSystem.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.messagingSystem.publish('NetworkController:infuraIsUnblocked'); } } From 67aad97a2f4997fdb79d0bd12292330898e935a4 Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Fri, 5 Sep 2025 08:42:42 -0600 Subject: [PATCH 10/10] Update JSDocs --- packages/network-controller/src/NetworkController.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/network-controller/src/NetworkController.ts b/packages/network-controller/src/NetworkController.ts index 6a6b775a4e9..26560884dd9 100644 --- a/packages/network-controller/src/NetworkController.ts +++ b/packages/network-controller/src/NetworkController.ts @@ -1568,8 +1568,7 @@ export class NetworkController extends BaseController< * * @param networkClientId - The ID of the network client to inspect. * If no ID is provided, uses the currently selected network. - * @returns The resulting metadata for the network, or null if the network is - * switched while the latest block is being fetched. + * @returns The resulting metadata for the network. */ async #determineNetworkMetadata(networkClientId: NetworkClientId) { // Force TypeScript to use one of the two overloads explicitly