From 60ade5751eb3bd56d664c707cbd52b7627a02061 Mon Sep 17 00:00:00 2001 From: Salah Date: Wed, 15 Mar 2023 19:52:57 +0100 Subject: [PATCH 1/4] implement reset connection in network controller --- packages/network-controller/src/NetworkController.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/network-controller/src/NetworkController.ts b/packages/network-controller/src/NetworkController.ts index b81cb262dc..d280c457cf 100644 --- a/packages/network-controller/src/NetworkController.ts +++ b/packages/network-controller/src/NetworkController.ts @@ -530,6 +530,12 @@ export class NetworkController extends BaseControllerV2< return isEIP1559Compatible; } + resetConnection() { + const { type, rpcTarget, chainId, ticker, nickname } = + this.state.providerConfig; + this.configureProvider(type, rpcTarget, chainId, ticker, nickname); + } + #setProviderAndBlockTracker({ provider, blockTracker, From 4104f4032542c82a8b9872bedb0aba494c88d135 Mon Sep 17 00:00:00 2001 From: Salah Date: Thu, 6 Apr 2023 17:00:29 +0000 Subject: [PATCH 2/4] fix: unit tests --- .../tests/NetworkController.test.ts | 441 ++++++++++++++++++ 1 file changed, 441 insertions(+) diff --git a/packages/network-controller/tests/NetworkController.test.ts b/packages/network-controller/tests/NetworkController.test.ts index f6a6e615f7..5d6baf72bf 100644 --- a/packages/network-controller/tests/NetworkController.test.ts +++ b/packages/network-controller/tests/NetworkController.test.ts @@ -3496,6 +3496,447 @@ describe('NetworkController', () => { }); }); + describe('resetConnection', () => { + [NetworkType.mainnet, NetworkType.goerli, NetworkType.sepolia].forEach( + (networkType) => { + describe(`when the type in the provider configuration is "${networkType}"`, () => { + it('sets isCustomNetwork in state to false', async () => { + const messenger = buildMessenger(); + await withController( + { + messenger, + state: { + isCustomNetwork: true, + }, + infuraProjectId: 'infura-project-id', + }, + async ({ controller }) => { + const fakeInfuraProvider = buildFakeInfuraProvider(); + createInfuraProviderMock.mockReturnValue(fakeInfuraProvider); + const fakeInfuraSubprovider = buildFakeInfuraSubprovider(); + SubproviderMock.mockReturnValue(fakeInfuraSubprovider); + const fakeMetamaskProvider = buildFakeMetamaskProvider(); + createMetamaskProviderMock.mockReturnValue( + fakeMetamaskProvider, + ); + + await waitForStateChanges(messenger, { + propertyPath: ['isCustomNetwork'], + produceStateChanges: () => { + controller.resetConnection(); + }, + }); + + expect(controller.state.isCustomNetwork).toBe(false); + }, + ); + }); + + it('updates networkDetails.isEIP1559Compatible in state based on the latest block (assuming that the request for eth_getBlockByNumber is made successfully)', async () => { + const messenger = buildMessenger(); + await withController( + { + messenger, + state: { + providerConfig: buildProviderConfig(), + }, + }, + async ({ controller }) => { + const fakeMetamaskProvider = buildFakeMetamaskProvider([ + { + request: { + method: 'eth_getBlockByNumber', + params: ['latest', false], + }, + response: { + result: { + baseFeePerGas: '0x1', + }, + }, + }, + ]); + createMetamaskProviderMock.mockReturnValue( + fakeMetamaskProvider, + ); + + await waitForStateChanges(messenger, { + propertyPath: ['networkDetails', 'isEIP1559Compatible'], + produceStateChanges: () => { + controller.resetConnection(); + }, + }); + + expect( + controller.state.networkDetails.isEIP1559Compatible, + ).toBe(true); + }, + ); + }); + + it(`initializes a new provider object pointed to the current Infura network (type: "${networkType}")`, async () => { + await withController( + { + state: { + providerConfig: { + type: networkType, + // NOTE: This doesn't need to match the logical chain ID of + // the network selected, it just needs to exist + chainId: '0x9999999', + }, + }, + infuraProjectId: 'infura-project-id', + }, + async ({ controller }) => { + const fakeInfuraProvider = buildFakeInfuraProvider(); + createInfuraProviderMock.mockReturnValue(fakeInfuraProvider); + const fakeInfuraSubprovider = buildFakeInfuraSubprovider(); + SubproviderMock.mockReturnValue(fakeInfuraSubprovider); + const fakeMetamaskProvider = buildFakeMetamaskProvider([ + { + request: { + method: 'eth_chainId', + }, + response: { + result: '0x1337', + }, + }, + ]); + createMetamaskProviderMock.mockReturnValue( + fakeMetamaskProvider, + ); + + controller.resetConnection(); + + const { provider } = controller.getProviderAndBlockTracker(); + const promisifiedSendAsync = promisify(provider.sendAsync).bind( + provider, + ); + const { result: chainIdResult } = await promisifiedSendAsync({ + method: 'eth_chainId', + }); + expect(chainIdResult).toBe('0x1337'); + }, + ); + }); + + it('replaces the provider object underlying the provider proxy without creating a new instance of the proxy itself', async () => { + await withController( + { + state: { + providerConfig: { + type: networkType, + // NOTE: This doesn't need to match the logical chain ID of + // the network selected, it just needs to exist + chainId: '0x9999999', + }, + }, + }, + async ({ controller }) => { + const fakeInfuraProvider = buildFakeInfuraProvider(); + createInfuraProviderMock.mockReturnValue(fakeInfuraProvider); + const fakeInfuraSubprovider = buildFakeInfuraSubprovider(); + SubproviderMock.mockReturnValue(fakeInfuraSubprovider); + const fakeMetamaskProvider = buildFakeMetamaskProvider(); + createMetamaskProviderMock.mockReturnValue( + fakeMetamaskProvider, + ); + + controller.initializeProvider(); + + const { provider: providerBefore } = + controller.getProviderAndBlockTracker(); + controller.resetConnection(); + const { provider: providerAfter } = + controller.getProviderAndBlockTracker(); + + expect(providerBefore).toBe(providerAfter); + }, + ); + }); + + it('ensures that the existing provider is stopped while replacing it', async () => { + await withController( + { + state: { + providerConfig: { + type: networkType, + // NOTE: This doesn't need to match the logical chain ID of + // the network selected, it just needs to exist + chainId: '0x9999999', + }, + }, + }, + ({ controller }) => { + const fakeInfuraProvider = buildFakeInfuraProvider(); + createInfuraProviderMock.mockReturnValue(fakeInfuraProvider); + const fakeInfuraSubprovider = buildFakeInfuraSubprovider(); + SubproviderMock.mockReturnValue(fakeInfuraSubprovider); + const fakeMetamaskProviders = [ + buildFakeMetamaskProvider(), + buildFakeMetamaskProvider(), + ]; + jest.spyOn(fakeMetamaskProviders[0], 'stop'); + createMetamaskProviderMock + .mockImplementationOnce(() => fakeMetamaskProviders[0]) + .mockImplementationOnce(() => fakeMetamaskProviders[1]); + + controller.resetConnection(); + controller.resetConnection(); + assert(controller.getProviderAndBlockTracker().provider); + jest.runAllTimers(); + + expect(fakeMetamaskProviders[0].stop).toHaveBeenCalled(); + }, + ); + }); + }); + }, + ); + + describe(`when the type in the provider configuration is "rpc"`, () => { + it('initializes a new provider object pointed to the same RPC URL as the current network and using the same chain ID', async () => { + await withController( + { + state: { + providerConfig: { + type: NetworkType.rpc, + rpcTarget: 'https://mock-rpc-url', + chainId: '0x1337', + ticker: 'TEST', + id: 'testNetworkConfigurationId', + }, + networkConfigurations: { + testNetworkConfigurationId: { + rpcUrl: 'https://mock-rpc-url', + chainId: '0x1337', + ticker: 'TEST', + id: 'testNetworkConfigurationId', + }, + }, + }, + }, + async ({ controller }) => { + const fakeInfuraProvider = buildFakeInfuraProvider(); + createInfuraProviderMock.mockReturnValue(fakeInfuraProvider); + const fakeInfuraSubprovider = buildFakeInfuraSubprovider(); + SubproviderMock.mockReturnValue(fakeInfuraSubprovider); + const fakeMetamaskProvider = buildFakeMetamaskProvider([ + { + request: { + method: 'eth_chainId', + }, + response: { + result: '0x1337', + }, + }, + ]); + createMetamaskProviderMock.mockReturnValue(fakeMetamaskProvider); + + controller.resetConnection(); + const { provider } = controller.getProviderAndBlockTracker(); + const promisifiedSendAsync = promisify(provider.sendAsync).bind( + provider, + ); + const { result: chainIdResult } = await promisifiedSendAsync({ + method: 'eth_chainId', + }); + expect(chainIdResult).toBe('0x1337'); + }, + ); + }); + + it('sets isCustomNetwork in state to true', async () => { + const messenger = buildMessenger(); + await withController( + { + messenger, + state: { + isCustomNetwork: false, + providerConfig: { + type: NetworkType.rpc, + rpcTarget: 'https://mock-rpc-url', + chainId: '0x1337', + ticker: 'TEST', + id: 'testNetworkConfigurationId', + }, + }, + infuraProjectId: 'infura-project-id', + }, + async ({ controller }) => { + const fakeInfuraProvider = buildFakeInfuraProvider(); + createInfuraProviderMock.mockReturnValue(fakeInfuraProvider); + const fakeInfuraSubprovider = buildFakeInfuraSubprovider(); + SubproviderMock.mockReturnValue(fakeInfuraSubprovider); + const fakeMetamaskProvider = buildFakeMetamaskProvider(); + createMetamaskProviderMock.mockReturnValue(fakeMetamaskProvider); + + await waitForStateChanges(messenger, { + propertyPath: ['isCustomNetwork'], + produceStateChanges: () => { + controller.resetConnection(); + }, + }); + + expect(controller.state.isCustomNetwork).toBe(true); + }, + ); + }); + + it('replaces the provider object underlying the provider proxy without creating a new instance of the proxy itself', async () => { + const messenger = buildMessenger(); + await withController( + { + messenger, + state: { + providerConfig: { + type: NetworkType.rpc, + rpcTarget: 'https://mock-rpc-url', + chainId: '0xtest', + ticker: 'TEST', + id: 'testNetworkConfigurationId', + }, + networkConfigurations: { + testNetworkConfigurationId: { + rpcUrl: 'https://mock-rpc-url', + chainId: '0xtest', + ticker: 'TEST', + id: 'testNetworkConfigurationId', + }, + }, + }, + }, + async ({ controller }) => { + const fakeMetamaskProvider = buildFakeMetamaskProvider([ + { + request: { + method: 'eth_getBlockByNumber', + params: ['latest', false], + }, + response: { + result: { + baseFeePerGas: '0x1', + }, + }, + }, + { + request: { + method: 'eth_getBlockByNumber', + params: ['latest', false], + }, + response: { + result: { + baseFeePerGas: '0x1', + }, + }, + }, + ]); + createMetamaskProviderMock.mockReturnValue(fakeMetamaskProvider); + + controller.initializeProvider(); + const { provider: providerBefore } = + controller.getProviderAndBlockTracker(); + await waitForStateChanges(messenger, { + propertyPath: ['networkDetails', 'isEIP1559Compatible'], + produceStateChanges: () => { + controller.resetConnection(); + }, + }); + const { provider: providerAfter } = + controller.getProviderAndBlockTracker(); + expect(providerBefore).toBe(providerAfter); + }, + ); + }); + + it('updates networkDetails.isEIP1559Compatible in state based on the latest block (assuming that the request for eth_getBlockByNumber is made successfully)', async () => { + const messenger = buildMessenger(); + await withController( + { + messenger, + state: { + providerConfig: { + type: NetworkType.rpc, + rpcTarget: 'https://mock-rpc-url', + chainId: '0xtest', + ticker: 'TEST', + id: 'testNetworkConfigurationId', + }, + networkDetails: { + isEIP1559Compatible: false, + }, + }, + }, + async ({ controller }) => { + const fakeMetamaskProvider = buildFakeMetamaskProvider([ + { + request: { + method: 'eth_getBlockByNumber', + params: ['latest', false], + }, + response: { + result: { + baseFeePerGas: '0x1', + }, + }, + }, + ]); + createMetamaskProviderMock.mockReturnValue(fakeMetamaskProvider); + + expect(controller.state.networkDetails).toStrictEqual({ + isEIP1559Compatible: false, + }); + await waitForStateChanges(messenger, { + propertyPath: ['networkDetails', 'isEIP1559Compatible'], + produceStateChanges: () => { + controller.resetConnection(); + }, + }); + expect(controller.state.networkDetails).toStrictEqual({ + isEIP1559Compatible: true, + }); + }, + ); + }); + + it('ensures that the existing provider is stopped while replacing it', async () => { + await withController( + { + state: { + providerConfig: { + type: NetworkType.rpc, + rpcTarget: 'https://mock-rpc-url', + chainId: '0xtest', + ticker: 'TEST', + id: 'testNetworkConfigurationId', + }, + }, + }, + ({ controller }) => { + const fakeInfuraProvider = buildFakeInfuraProvider(); + createInfuraProviderMock.mockReturnValue(fakeInfuraProvider); + const fakeInfuraSubprovider = buildFakeInfuraSubprovider(); + SubproviderMock.mockReturnValue(fakeInfuraSubprovider); + const fakeMetamaskProviders = [ + buildFakeMetamaskProvider(), + buildFakeMetamaskProvider(), + ]; + jest.spyOn(fakeMetamaskProviders[0], 'stop'); + createMetamaskProviderMock + .mockImplementationOnce(() => fakeMetamaskProviders[0]) + .mockImplementationOnce(() => fakeMetamaskProviders[1]); + + controller.resetConnection(); + controller.resetConnection(); + assert(controller.getProviderAndBlockTracker().provider); + jest.runAllTimers(); + + expect(fakeMetamaskProviders[0].stop).toHaveBeenCalled(); + }, + ); + }); + }); + }); + describe('NetworkController:getProviderConfig action', () => { it('returns the provider config in state', async () => { const messenger = buildMessenger(); From 72a99b5820519bbf5f367e7e036626f02e67a279 Mon Sep 17 00:00:00 2001 From: Salah Date: Thu, 6 Apr 2023 17:28:36 +0000 Subject: [PATCH 3/4] fix: unit tests --- .../tests/NetworkController.test.ts | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) diff --git a/packages/network-controller/tests/NetworkController.test.ts b/packages/network-controller/tests/NetworkController.test.ts index 5d6baf72bf..71f21c211f 100644 --- a/packages/network-controller/tests/NetworkController.test.ts +++ b/packages/network-controller/tests/NetworkController.test.ts @@ -3689,6 +3689,56 @@ describe('NetworkController', () => { }, ); }); + + describe('when an "error" event occurs on the new provider', () => { + it('retrieves the network version and, assuming success, persists it to state', async () => { + const messenger = buildMessenger(); + await withController( + { + messenger, + state: { + providerConfig: { + type: networkType, + // NOTE: This doesn't need to match the logical chain ID of + // the network selected, it just needs to exist + chainId: '0x9999999', + }, + }, + }, + async ({ controller }) => { + const fakeMetamaskProvider = buildFakeMetamaskProvider([ + { + request: { + method: 'net_version', + }, + response: { + result: '42', + }, + }, + ]); + createMetamaskProviderMock.mockReturnValue( + fakeMetamaskProvider, + ); + + controller.resetConnection(); + assert(controller.getProviderAndBlockTracker().provider); + + expect(controller.state.network).toBe('loading'); + + await waitForStateChanges(messenger, { + propertyPath: ['network'], + produceStateChanges: () => { + controller + .getProviderAndBlockTracker() + .provider.emit('error', { some: 'error' }); + }, + }); + + expect(controller.state.network).toBe('42'); + }, + ); + }); + }); }); }, ); @@ -3934,6 +3984,55 @@ describe('NetworkController', () => { }, ); }); + + describe('when an "error" event occurs on the new provider', () => { + it('retrieves the network version and, assuming success, persists it to state', async () => { + const messenger = buildMessenger(); + await withController( + { + messenger, + state: { + providerConfig: { + type: NetworkType.rpc, + rpcTarget: 'https://mock-rpc-url', + chainId: '0xtest', + ticker: 'TEST', + id: 'testNetworkConfigurationId', + }, + }, + }, + async ({ controller }) => { + const fakeMetamaskProvider = buildFakeMetamaskProvider([ + { + request: { + method: 'net_version', + }, + response: { + result: '42', + }, + }, + ]); + createMetamaskProviderMock.mockReturnValue(fakeMetamaskProvider); + + controller.resetConnection(); + assert(controller.getProviderAndBlockTracker().provider); + + expect(controller.state.network).toBe('loading'); + + await waitForStateChanges(messenger, { + propertyPath: ['network'], + produceStateChanges: () => { + controller + .getProviderAndBlockTracker() + .provider.emit('error', { some: 'error' }); + }, + }); + + expect(controller.state.network).toBe('42'); + }, + ); + }); + }); }); }); From 1c982382cf7a1d6d01e852f15f58bed2a6006fb1 Mon Sep 17 00:00:00 2001 From: Salah Date: Thu, 6 Apr 2023 17:35:40 +0000 Subject: [PATCH 4/4] fix: unit tests order --- .../tests/NetworkController.test.ts | 206 +++++++++--------- 1 file changed, 103 insertions(+), 103 deletions(-) diff --git a/packages/network-controller/tests/NetworkController.test.ts b/packages/network-controller/tests/NetworkController.test.ts index 71f21c211f..301d54a354 100644 --- a/packages/network-controller/tests/NetworkController.test.ts +++ b/packages/network-controller/tests/NetworkController.test.ts @@ -3532,47 +3532,6 @@ describe('NetworkController', () => { ); }); - it('updates networkDetails.isEIP1559Compatible in state based on the latest block (assuming that the request for eth_getBlockByNumber is made successfully)', async () => { - const messenger = buildMessenger(); - await withController( - { - messenger, - state: { - providerConfig: buildProviderConfig(), - }, - }, - async ({ controller }) => { - const fakeMetamaskProvider = buildFakeMetamaskProvider([ - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - response: { - result: { - baseFeePerGas: '0x1', - }, - }, - }, - ]); - createMetamaskProviderMock.mockReturnValue( - fakeMetamaskProvider, - ); - - await waitForStateChanges(messenger, { - propertyPath: ['networkDetails', 'isEIP1559Compatible'], - produceStateChanges: () => { - controller.resetConnection(); - }, - }); - - expect( - controller.state.networkDetails.isEIP1559Compatible, - ).toBe(true); - }, - ); - }); - it(`initializes a new provider object pointed to the current Infura network (type: "${networkType}")`, async () => { await withController( { @@ -3619,6 +3578,47 @@ describe('NetworkController', () => { ); }); + it('updates networkDetails.isEIP1559Compatible in state based on the latest block (assuming that the request for eth_getBlockByNumber is made successfully)', async () => { + const messenger = buildMessenger(); + await withController( + { + messenger, + state: { + providerConfig: buildProviderConfig(), + }, + }, + async ({ controller }) => { + const fakeMetamaskProvider = buildFakeMetamaskProvider([ + { + request: { + method: 'eth_getBlockByNumber', + params: ['latest', false], + }, + response: { + result: { + baseFeePerGas: '0x1', + }, + }, + }, + ]); + createMetamaskProviderMock.mockReturnValue( + fakeMetamaskProvider, + ); + + await waitForStateChanges(messenger, { + propertyPath: ['networkDetails', 'isEIP1559Compatible'], + produceStateChanges: () => { + controller.resetConnection(); + }, + }); + + expect( + controller.state.networkDetails.isEIP1559Compatible, + ).toBe(true); + }, + ); + }); + it('replaces the provider object underlying the provider proxy without creating a new instance of the proxy itself', async () => { await withController( { @@ -3744,6 +3744,43 @@ describe('NetworkController', () => { ); describe(`when the type in the provider configuration is "rpc"`, () => { + it('sets isCustomNetwork in state to true', async () => { + const messenger = buildMessenger(); + await withController( + { + messenger, + state: { + isCustomNetwork: false, + providerConfig: { + type: NetworkType.rpc, + rpcTarget: 'https://mock-rpc-url', + chainId: '0x1337', + ticker: 'TEST', + id: 'testNetworkConfigurationId', + }, + }, + infuraProjectId: 'infura-project-id', + }, + async ({ controller }) => { + const fakeInfuraProvider = buildFakeInfuraProvider(); + createInfuraProviderMock.mockReturnValue(fakeInfuraProvider); + const fakeInfuraSubprovider = buildFakeInfuraSubprovider(); + SubproviderMock.mockReturnValue(fakeInfuraSubprovider); + const fakeMetamaskProvider = buildFakeMetamaskProvider(); + createMetamaskProviderMock.mockReturnValue(fakeMetamaskProvider); + + await waitForStateChanges(messenger, { + propertyPath: ['isCustomNetwork'], + produceStateChanges: () => { + controller.resetConnection(); + }, + }); + + expect(controller.state.isCustomNetwork).toBe(true); + }, + ); + }); + it('initializes a new provider object pointed to the same RPC URL as the current network and using the same chain ID', async () => { await withController( { @@ -3795,39 +3832,52 @@ describe('NetworkController', () => { ); }); - it('sets isCustomNetwork in state to true', async () => { + it('updates networkDetails.isEIP1559Compatible in state based on the latest block (assuming that the request for eth_getBlockByNumber is made successfully)', async () => { const messenger = buildMessenger(); await withController( { messenger, state: { - isCustomNetwork: false, providerConfig: { type: NetworkType.rpc, rpcTarget: 'https://mock-rpc-url', - chainId: '0x1337', + chainId: '0xtest', ticker: 'TEST', id: 'testNetworkConfigurationId', }, + networkDetails: { + isEIP1559Compatible: false, + }, }, - infuraProjectId: 'infura-project-id', }, async ({ controller }) => { - const fakeInfuraProvider = buildFakeInfuraProvider(); - createInfuraProviderMock.mockReturnValue(fakeInfuraProvider); - const fakeInfuraSubprovider = buildFakeInfuraSubprovider(); - SubproviderMock.mockReturnValue(fakeInfuraSubprovider); - const fakeMetamaskProvider = buildFakeMetamaskProvider(); + const fakeMetamaskProvider = buildFakeMetamaskProvider([ + { + request: { + method: 'eth_getBlockByNumber', + params: ['latest', false], + }, + response: { + result: { + baseFeePerGas: '0x1', + }, + }, + }, + ]); createMetamaskProviderMock.mockReturnValue(fakeMetamaskProvider); + expect(controller.state.networkDetails).toStrictEqual({ + isEIP1559Compatible: false, + }); await waitForStateChanges(messenger, { - propertyPath: ['isCustomNetwork'], + propertyPath: ['networkDetails', 'isEIP1559Compatible'], produceStateChanges: () => { controller.resetConnection(); }, }); - - expect(controller.state.isCustomNetwork).toBe(true); + expect(controller.state.networkDetails).toStrictEqual({ + isEIP1559Compatible: true, + }); }, ); }); @@ -3898,56 +3948,6 @@ describe('NetworkController', () => { ); }); - it('updates networkDetails.isEIP1559Compatible in state based on the latest block (assuming that the request for eth_getBlockByNumber is made successfully)', async () => { - const messenger = buildMessenger(); - await withController( - { - messenger, - state: { - providerConfig: { - type: NetworkType.rpc, - rpcTarget: 'https://mock-rpc-url', - chainId: '0xtest', - ticker: 'TEST', - id: 'testNetworkConfigurationId', - }, - networkDetails: { - isEIP1559Compatible: false, - }, - }, - }, - async ({ controller }) => { - const fakeMetamaskProvider = buildFakeMetamaskProvider([ - { - request: { - method: 'eth_getBlockByNumber', - params: ['latest', false], - }, - response: { - result: { - baseFeePerGas: '0x1', - }, - }, - }, - ]); - createMetamaskProviderMock.mockReturnValue(fakeMetamaskProvider); - - expect(controller.state.networkDetails).toStrictEqual({ - isEIP1559Compatible: false, - }); - await waitForStateChanges(messenger, { - propertyPath: ['networkDetails', 'isEIP1559Compatible'], - produceStateChanges: () => { - controller.resetConnection(); - }, - }); - expect(controller.state.networkDetails).toStrictEqual({ - isEIP1559Compatible: true, - }); - }, - ); - }); - it('ensures that the existing provider is stopped while replacing it', async () => { await withController( {