From 4b26185ade9b4a27605313957c381db0f018ce1d Mon Sep 17 00:00:00 2001 From: tommasini Date: Thu, 13 Jun 2024 15:00:30 +0100 Subject: [PATCH 1/4] added migration that populates the missing ids on network controller state --- app/store/migrations/043.test.ts | 396 +++++++++++++++++++++++++++++++ app/store/migrations/043.ts | 70 ++++++ app/store/migrations/index.ts | 2 + 3 files changed, 468 insertions(+) create mode 100644 app/store/migrations/043.test.ts create mode 100644 app/store/migrations/043.ts diff --git a/app/store/migrations/043.test.ts b/app/store/migrations/043.test.ts new file mode 100644 index 00000000000..1440bbe9374 --- /dev/null +++ b/app/store/migrations/043.test.ts @@ -0,0 +1,396 @@ +import migrate from './043'; +import { merge } from 'lodash'; +import { captureException } from '@sentry/react-native'; +import initialRootState from '../../util/test/initial-root-state'; + +const oldState = { + engine: { + backgroundState: { + NetworkController: { + networkConfigurations: { + '92c0e479-6133-4a18-b1bf-fa38f654e293': { + rpcUrl: + 'https://polygon-mainnet.infura.io/v3/d039103314584a379e33c21fbe89b6cb', + chainId: '0x89', + ticker: 'MATIC', + nickname: 'Polygon Mainnet', + rpcPrefs: { + blockExplorerUrl: 'https://polygonscan.com', + }, + }, + '8229552c-e0ab-4337-b2c7-c572c2dc5f5a': { + rpcUrl: + 'https://optimism-mainnet.infura.io/v3/d039103314584a379e33c21fbe89b6cb', + chainId: '0xa', + ticker: 'ETH', + nickname: 'Optimism', + rpcPrefs: { + blockExplorerUrl: 'https://optimistic.etherscan.io', + }, + }, + '97d6b2d3-40e6-4813-aede-461b29ead719': { + rpcUrl: 'https://forno.celo.org/', + chainId: '0xa4ec', + ticker: 'CELO', + nickname: 'Celo (Mainnet)', + rpcPrefs: { + blockExplorerUrl: 'https://explorer.celo.org/', + }, + }, + 'e2daf1b8-67c2-4de5-bead-fb3bbecfc607': { + rpcUrl: 'https://rpc.gnosischain.com/', + chainId: '0x64', + ticker: 'XDAI', + nickname: 'Gnosis', + rpcPrefs: { + blockExplorerUrl: 'https://gnosisscan.io', + }, + }, + '581302a2-f713-40fd-9175-e25392b49a6e': { + rpcUrl: + 'https://arbitrum-mainnet.infura.io/v3/267e54bc7b094f3f817b941097d249d8', + chainId: '0xa4b1', + ticker: 'ETH', + nickname: 'Arbitrum One', + rpcPrefs: { + blockExplorerUrl: 'https://arbiscan.io', + }, + }, + 'a90c203e-8e27-415b-94e5-5d7f933d752a': { + rpcUrl: 'https://api.avax.network/ext/bc/C/rpc', + chainId: '0xa86a', + ticker: 'AVAX', + nickname: 'Avalanche', + rpcPrefs: { + blockExplorerUrl: 'https://snowtrace.io', + }, + }, + 'a50a052d-b0e3-48f7-b1d2-e795fb52b485': { + rpcUrl: 'https://developer-access-mainnet.base.org', + chainId: '0x2105', + ticker: 'ETH', + nickname: 'Base', + rpcPrefs: { + blockExplorerUrl: 'https://basescan.org', + }, + }, + '79377077-52dd-4960-9026-879ba34b0f26': { + rpcUrl: 'https://zksync2-mainnet.zksync.io', + chainId: '0x144', + ticker: 'ETH', + nickname: 'zkSync Era', + rpcPrefs: { + blockExplorerUrl: 'https://explorer.zksync.io', + }, + }, + '44bcb458-43bc-4c74-9a6b-3c85aa8e5e55': { + rpcUrl: 'https://rpc.zora.energy', + chainId: '0x76adf1', + ticker: 'ETH', + nickname: 'Zora', + rpcPrefs: { + blockExplorerUrl: 'https://explorer.zora.energy', + }, + }, + '5ce58544-8df5-46c2-a7b2-8d79fdd88241': { + rpcUrl: 'https://sepolia.era.zksync.dev', + chainId: '0x12c', + ticker: 'ETH', + nickname: 'zkSync Sepolia Testnet', + rpcPrefs: { + blockExplorerUrl: 'https://sepolia.explorer.zksync.io/', + }, + id: '5ce58544-8df5-46c2-a7b2-8d79fdd88241', + }, + 'c15d0a4c-ded8-4155-964a-a9613e70c3dc': { + id: 'c15d0a4c-ded8-4155-964a-a9613e70c3dc', + rpcUrl: 'https://rpc.degen.tips', + chainId: '0x27bc86aa', + ticker: 'DEGEN', + nickname: 'Degen', + rpcPrefs: { + blockExplorerUrl: 'https://explorer.degen.tips', + }, + }, + 'd73386c9-308f-49c7-a566-a1a513780e8e': { + id: 'd73386c9-308f-49c7-a566-a1a513780e8e', + rpcUrl: 'https://bsc-dataseed1.binance.org/', + chainId: '0x38', + ticker: 'BNB', + nickname: 'BNB Smart Chain', + rpcPrefs: { + blockExplorerUrl: 'https://bscscan.com', + }, + }, + '411511b5-6e6c-4e30-8563-d67c4d0126b4': { + id: '411511b5-6e6c-4e30-8563-d67c4d0126b4', + rpcUrl: 'https://rpc.ham.fun', + chainId: '0x13f8', + ticker: 'ETH', + nickname: 'Ham', + rpcPrefs: { + blockExplorerUrl: 'https://explorer.ham.fun', + }, + }, + }, + providerConfig: { + type: 'rpc', + ticker: 'ETH', + chainId: '0x2105', + rpcPrefs: { + blockExplorerUrl: 'https://basescan.org', + }, + rpcUrl: 'https://developer-access-mainnet.base.org', + nickname: 'Base', + }, + }, + }, + }, +}; + +const expectedState = { + engine: { + backgroundState: { + NetworkController: { + selectedNetworkClientId: 'a50a052d-b0e3-48f7-b1d2-e795fb52b485', + networkConfigurations: { + '92c0e479-6133-4a18-b1bf-fa38f654e293': { + id: '92c0e479-6133-4a18-b1bf-fa38f654e293', + rpcUrl: + 'https://polygon-mainnet.infura.io/v3/d039103314584a379e33c21fbe89b6cb', + chainId: '0x89', + ticker: 'MATIC', + nickname: 'Polygon Mainnet', + rpcPrefs: { + blockExplorerUrl: 'https://polygonscan.com', + }, + }, + '8229552c-e0ab-4337-b2c7-c572c2dc5f5a': { + id: '8229552c-e0ab-4337-b2c7-c572c2dc5f5a', + rpcUrl: + 'https://optimism-mainnet.infura.io/v3/d039103314584a379e33c21fbe89b6cb', + chainId: '0xa', + ticker: 'ETH', + nickname: 'Optimism', + rpcPrefs: { + blockExplorerUrl: 'https://optimistic.etherscan.io', + }, + }, + '97d6b2d3-40e6-4813-aede-461b29ead719': { + id: '97d6b2d3-40e6-4813-aede-461b29ead719', + rpcUrl: 'https://forno.celo.org/', + chainId: '0xa4ec', + ticker: 'CELO', + nickname: 'Celo (Mainnet)', + rpcPrefs: { + blockExplorerUrl: 'https://explorer.celo.org/', + }, + }, + 'e2daf1b8-67c2-4de5-bead-fb3bbecfc607': { + id: 'e2daf1b8-67c2-4de5-bead-fb3bbecfc607', + rpcUrl: 'https://rpc.gnosischain.com/', + chainId: '0x64', + ticker: 'XDAI', + nickname: 'Gnosis', + rpcPrefs: { + blockExplorerUrl: 'https://gnosisscan.io', + }, + }, + '581302a2-f713-40fd-9175-e25392b49a6e': { + id: '581302a2-f713-40fd-9175-e25392b49a6e', + rpcUrl: + 'https://arbitrum-mainnet.infura.io/v3/267e54bc7b094f3f817b941097d249d8', + chainId: '0xa4b1', + ticker: 'ETH', + nickname: 'Arbitrum One', + rpcPrefs: { + blockExplorerUrl: 'https://arbiscan.io', + }, + }, + 'a90c203e-8e27-415b-94e5-5d7f933d752a': { + id: 'a90c203e-8e27-415b-94e5-5d7f933d752a', + rpcUrl: 'https://api.avax.network/ext/bc/C/rpc', + chainId: '0xa86a', + ticker: 'AVAX', + nickname: 'Avalanche', + rpcPrefs: { + blockExplorerUrl: 'https://snowtrace.io', + }, + }, + 'a50a052d-b0e3-48f7-b1d2-e795fb52b485': { + id: 'a50a052d-b0e3-48f7-b1d2-e795fb52b485', + rpcUrl: 'https://developer-access-mainnet.base.org', + chainId: '0x2105', + ticker: 'ETH', + nickname: 'Base', + rpcPrefs: { + blockExplorerUrl: 'https://basescan.org', + }, + }, + '79377077-52dd-4960-9026-879ba34b0f26': { + id: '79377077-52dd-4960-9026-879ba34b0f26', + rpcUrl: 'https://zksync2-mainnet.zksync.io', + chainId: '0x144', + ticker: 'ETH', + nickname: 'zkSync Era', + rpcPrefs: { + blockExplorerUrl: 'https://explorer.zksync.io', + }, + }, + '44bcb458-43bc-4c74-9a6b-3c85aa8e5e55': { + id: '44bcb458-43bc-4c74-9a6b-3c85aa8e5e55', + rpcUrl: 'https://rpc.zora.energy', + chainId: '0x76adf1', + ticker: 'ETH', + nickname: 'Zora', + rpcPrefs: { + blockExplorerUrl: 'https://explorer.zora.energy', + }, + }, + '5ce58544-8df5-46c2-a7b2-8d79fdd88241': { + rpcUrl: 'https://sepolia.era.zksync.dev', + chainId: '0x12c', + ticker: 'ETH', + nickname: 'zkSync Sepolia Testnet', + rpcPrefs: { + blockExplorerUrl: 'https://sepolia.explorer.zksync.io/', + }, + id: '5ce58544-8df5-46c2-a7b2-8d79fdd88241', + }, + 'c15d0a4c-ded8-4155-964a-a9613e70c3dc': { + id: 'c15d0a4c-ded8-4155-964a-a9613e70c3dc', + rpcUrl: 'https://rpc.degen.tips', + chainId: '0x27bc86aa', + ticker: 'DEGEN', + nickname: 'Degen', + rpcPrefs: { + blockExplorerUrl: 'https://explorer.degen.tips', + }, + }, + 'd73386c9-308f-49c7-a566-a1a513780e8e': { + id: 'd73386c9-308f-49c7-a566-a1a513780e8e', + rpcUrl: 'https://bsc-dataseed1.binance.org/', + chainId: '0x38', + ticker: 'BNB', + nickname: 'BNB Smart Chain', + rpcPrefs: { + blockExplorerUrl: 'https://bscscan.com', + }, + }, + '411511b5-6e6c-4e30-8563-d67c4d0126b4': { + id: '411511b5-6e6c-4e30-8563-d67c4d0126b4', + rpcUrl: 'https://rpc.ham.fun', + chainId: '0x13f8', + ticker: 'ETH', + nickname: 'Ham', + rpcPrefs: { + blockExplorerUrl: 'https://explorer.ham.fun', + }, + }, + }, + providerConfig: { + type: 'rpc', + ticker: 'ETH', + chainId: '0x2105', + rpcPrefs: { + blockExplorerUrl: 'https://basescan.org', + }, + rpcUrl: 'https://developer-access-mainnet.base.org', + nickname: 'Base', + id: 'a50a052d-b0e3-48f7-b1d2-e795fb52b485', + }, + }, + }, + }, +}; + +jest.mock('@sentry/react-native', () => ({ + captureException: jest.fn(), +})); +const mockedCaptureException = jest.mocked(captureException); + +describe('Migration #43', () => { + beforeEach(() => { + jest.restoreAllMocks(); + jest.resetAllMocks(); + }); + + const invalidStates = [ + { + state: null, + errorMessage: "FATAL ERROR: Migration 43: Invalid state error: 'object'", + scenario: 'state is invalid', + }, + { + state: merge({}, initialRootState, { + engine: null, + }), + errorMessage: + "FATAL ERROR: Migration 43: Invalid engine state error: 'object'", + scenario: 'engine state is invalid', + }, + { + state: merge({}, initialRootState, { + engine: { + backgroundState: null, + }, + }), + errorMessage: + "FATAL ERROR: Migration 43: Invalid engine backgroundState error: 'object'", + scenario: 'backgroundState is invalid', + }, + { + state: merge({}, initialRootState, { + engine: { + backgroundState: { NetworkController: null }, + }, + }), + errorMessage: "Migration 43: Invalid NetworkController state: 'object'", + scenario: 'NetworkController is invalid', + }, + { + state: merge({}, initialRootState, { + engine: { + backgroundState: { + NetworkController: { providerConfig: null }, + }, + }, + }), + errorMessage: + "Migration 43: Invalid NetworkController providerConfig state: 'object'", + scenario: 'providerConfig is invalid', + }, + { + state: merge({}, initialRootState, { + engine: { + backgroundState: { + NetworkController: { + networkConfigurations: null, + }, + }, + }, + }), + errorMessage: + "Migration 43: Invalid NetworkController networkConfigurations state: 'object'", + scenario: 'networkConfigurations is invalid', + }, + ]; + + for (const { errorMessage, scenario, state } of invalidStates) { + it(`should capture exception if ${scenario}`, () => { + const newState = migrate(state); + + expect(newState).toStrictEqual(state); + expect(mockedCaptureException).toHaveBeenCalledWith(expect.any(Error)); + expect(mockedCaptureException.mock.calls[0][0].message).toBe( + errorMessage, + ); + }); + } + + it('Should add selectNetworkClientId and ids to NetworkController state properties', () => { + const newState = migrate(oldState); + + expect(newState).toStrictEqual(expectedState); + }); +}); diff --git a/app/store/migrations/043.ts b/app/store/migrations/043.ts new file mode 100644 index 00000000000..58431328e9b --- /dev/null +++ b/app/store/migrations/043.ts @@ -0,0 +1,70 @@ +import { hasProperty, isObject } from '@metamask/utils'; +import { ensureValidState } from './util'; +import { captureException } from '@sentry/react-native'; +import { NetworkState } from '@metamask/network-controller'; + +export default function migrate(state: unknown) { + if (!ensureValidState(state, 43)) { + return state; + } + + const networkControllerState = state.engine.backgroundState + .NetworkController as NetworkState; + + if (!isObject(networkControllerState)) { + captureException( + new Error( + `Migration 43: Invalid NetworkController state: '${typeof networkControllerState}'`, + ), + ); + return state; + } + + if ( + !isObject(networkControllerState.networkConfigurations) || + !hasProperty(networkControllerState, 'networkConfigurations') + ) { + captureException( + new Error( + `Migration 43: Invalid NetworkController networkConfigurations state: '${typeof networkControllerState.networkConfigurations}'`, + ), + ); + return state; + } + + if ( + !isObject(networkControllerState.providerConfig) || + !hasProperty(networkControllerState, 'providerConfig') + ) { + captureException( + new Error( + `Migration 43: Invalid NetworkController providerConfig state: '${typeof networkControllerState.providerConfig}'`, + ), + ); + return state; + } + + Object.entries(networkControllerState.networkConfigurations).forEach( + ([key, value]) => { + if (!value.id) { + value.id = key; + } + }, + ); + + if (!networkControllerState.selectedNetworkClientId) { + const selectedNetworkId = Object.entries( + networkControllerState.networkConfigurations, + ).find( + ([, value]) => + value.rpcUrl === networkControllerState.providerConfig.rpcUrl, + )?.[0]; + + if (selectedNetworkId) { + networkControllerState.selectedNetworkClientId = selectedNetworkId; + networkControllerState.providerConfig.id = selectedNetworkId; + } + } + + return state; +} diff --git a/app/store/migrations/index.ts b/app/store/migrations/index.ts index 0118fde130f..0fda33fa36e 100644 --- a/app/store/migrations/index.ts +++ b/app/store/migrations/index.ts @@ -43,6 +43,7 @@ import migration39 from './039'; import migration40 from './040'; import migration41 from './041'; import migration42 from './042'; +import migration43 from './043'; type MigrationFunction = (state: unknown) => unknown; type AsyncMigrationFunction = (state: unknown) => Promise; @@ -98,6 +99,7 @@ export const migrationList: MigrationsList = { 40: migration40, 41: migration41, 42: migration42, + 43: migration43, }; // Enable both synchronous and asynchronous migrations From a33f05a9fab3268ba67b016fd36656342033f56c Mon Sep 17 00:00:00 2001 From: tommasini Date: Thu, 13 Jun 2024 16:24:12 +0100 Subject: [PATCH 2/4] modify migration to include fallback to mainet for selected network client id value and also to prioritize selected network client id be provider config id first instead of the key of a matching rpc url on networkConfigurations object --- app/store/migrations/043.test.ts | 135 ++++++++++++++++++++++++++++++- app/store/migrations/043.ts | 29 ++++--- 2 files changed, 151 insertions(+), 13 deletions(-) diff --git a/app/store/migrations/043.test.ts b/app/store/migrations/043.test.ts index 1440bbe9374..a71730365b2 100644 --- a/app/store/migrations/043.test.ts +++ b/app/store/migrations/043.test.ts @@ -388,9 +388,142 @@ describe('Migration #43', () => { }); } - it('Should add selectNetworkClientId and ids to NetworkController state properties', () => { + it('should add selectNetworkClientId and ids to NetworkController state properties', () => { const newState = migrate(oldState); expect(newState).toStrictEqual(expectedState); }); + it('should add select network client id with provider config id if it is available', () => { + const oldState2 = { + engine: { + backgroundState: { + NetworkController: { + networkConfigurations: { + 'a50a052d-b0e3-48f7-b1d2-e795fb52b485': { + rpcUrl: 'https://developer-access-mainnet.base.org', + chainId: '0x2105', + ticker: 'ETH', + nickname: 'Base', + rpcPrefs: { + blockExplorerUrl: 'https://basescan.org', + }, + }, + }, + providerConfig: { + type: 'rpc', + ticker: 'ETH', + chainId: '0x2105', + rpcPrefs: { + blockExplorerUrl: 'https://basescan.org', + }, + rpcUrl: 'https://developer-access-mainnet.base.org', + nickname: 'Base', + id: 'a50a052d-b0e3-48f7-b1d2-e795fb52b596', + }, + }, + }, + }, + }; + const expectedState2 = { + engine: { + backgroundState: { + NetworkController: { + selectedNetworkClientId: 'a50a052d-b0e3-48f7-b1d2-e795fb52b596', + networkConfigurations: { + 'a50a052d-b0e3-48f7-b1d2-e795fb52b485': { + id: 'a50a052d-b0e3-48f7-b1d2-e795fb52b485', + rpcUrl: 'https://developer-access-mainnet.base.org', + chainId: '0x2105', + ticker: 'ETH', + nickname: 'Base', + rpcPrefs: { + blockExplorerUrl: 'https://basescan.org', + }, + }, + }, + providerConfig: { + type: 'rpc', + ticker: 'ETH', + chainId: '0x2105', + rpcPrefs: { + blockExplorerUrl: 'https://basescan.org', + }, + rpcUrl: 'https://developer-access-mainnet.base.org', + nickname: 'Base', + id: 'a50a052d-b0e3-48f7-b1d2-e795fb52b596', + }, + }, + }, + }, + }; + const newState = migrate(oldState2); + + expect(newState).toStrictEqual(expectedState2); + }); + it('should default selected network id to mainnet if no id is found', () => { + const oldState3 = { + engine: { + backgroundState: { + NetworkController: { + networkConfigurations: { + 'a50a052d-b0e3-48f7-b1d2-e795fb52b485': { + rpcUrl: 'https://developer-access-mainnet.base.org', + chainId: '0x2105', + ticker: 'ETH', + nickname: 'Base', + rpcPrefs: { + blockExplorerUrl: 'https://basescan.org', + }, + }, + }, + providerConfig: { + type: 'rpc', + ticker: 'ETH', + chainId: '0x2105', + rpcPrefs: { + blockExplorerUrl: 'https://basescan.org', + }, + rpcUrl: 'https://not-match-rpc-url.com', + nickname: 'Base', + }, + }, + }, + }, + }; + const expectedState3 = { + engine: { + backgroundState: { + NetworkController: { + selectedNetworkClientId: 'mainnet', + networkConfigurations: { + 'a50a052d-b0e3-48f7-b1d2-e795fb52b485': { + id: 'a50a052d-b0e3-48f7-b1d2-e795fb52b485', + rpcUrl: 'https://developer-access-mainnet.base.org', + chainId: '0x2105', + ticker: 'ETH', + nickname: 'Base', + rpcPrefs: { + blockExplorerUrl: 'https://basescan.org', + }, + }, + }, + providerConfig: { + type: 'rpc', + ticker: 'ETH', + chainId: '0x2105', + rpcPrefs: { + blockExplorerUrl: 'https://basescan.org', + }, + rpcUrl: 'https://not-match-rpc-url.com', + nickname: 'Base', + }, + }, + }, + }, + }; + + const newState = migrate(oldState3); + + expect(newState).toStrictEqual(expectedState3); + }); }); diff --git a/app/store/migrations/043.ts b/app/store/migrations/043.ts index 58431328e9b..5d5707b07ab 100644 --- a/app/store/migrations/043.ts +++ b/app/store/migrations/043.ts @@ -1,15 +1,14 @@ import { hasProperty, isObject } from '@metamask/utils'; import { ensureValidState } from './util'; import { captureException } from '@sentry/react-native'; -import { NetworkState } from '@metamask/network-controller'; +import { InfuraNetworkType } from '@metamask/controller-utils'; export default function migrate(state: unknown) { if (!ensureValidState(state, 43)) { return state; } - const networkControllerState = state.engine.backgroundState - .NetworkController as NetworkState; + const networkControllerState = state.engine.backgroundState.NetworkController; if (!isObject(networkControllerState)) { captureException( @@ -45,24 +44,30 @@ export default function migrate(state: unknown) { } Object.entries(networkControllerState.networkConfigurations).forEach( - ([key, value]) => { - if (!value.id) { - value.id = key; + ([networkConfigurationId, networkConfiguration]) => { + if (isObject(networkConfiguration) && !networkConfiguration.id) { + networkConfiguration.id = networkConfigurationId; } }, ); if (!networkControllerState.selectedNetworkClientId) { - const selectedNetworkId = Object.entries( - networkControllerState.networkConfigurations, - ).find( - ([, value]) => - value.rpcUrl === networkControllerState.providerConfig.rpcUrl, - )?.[0]; + const rpcUrl = networkControllerState.providerConfig.rpcUrl; + + const selectedNetworkId = + networkControllerState.providerConfig.id ?? + Object.entries(networkControllerState.networkConfigurations).find( + ([, networkConfiguration]) => + isObject(networkConfiguration) && + networkConfiguration.rpcUrl === rpcUrl, + )?.[0]; if (selectedNetworkId) { networkControllerState.selectedNetworkClientId = selectedNetworkId; networkControllerState.providerConfig.id = selectedNetworkId; + } else { + networkControllerState.selectedNetworkClientId = + InfuraNetworkType.mainnet; } } From 85c8e4703048bc9002744eec8fdebb617c555844 Mon Sep 17 00:00:00 2001 From: tommasini Date: Thu, 13 Jun 2024 16:37:49 +0100 Subject: [PATCH 3/4] added ts doc --- app/store/migrations/043.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/store/migrations/043.ts b/app/store/migrations/043.ts index 5d5707b07ab..4da5bb3b217 100644 --- a/app/store/migrations/043.ts +++ b/app/store/migrations/043.ts @@ -3,6 +3,14 @@ import { ensureValidState } from './util'; import { captureException } from '@sentry/react-native'; import { InfuraNetworkType } from '@metamask/controller-utils'; +/** + * Migration to fix the "Engine does not exist" issue + * On this migration we populate selectedNetworkClientId property of Network Controller with provider config id + * or network configuration id if the rpc url matches the one on provider config + * or we default it to mainnet + * @param state Persisted Redux state + * @returns mutated state with selectNetworkClientId on Network Controller data + */ export default function migrate(state: unknown) { if (!ensureValidState(state, 43)) { return state; From 6c5ef34545109d9fa78a5b51e82394a8b5fb15ca Mon Sep 17 00:00:00 2001 From: tommasini Date: Thu, 13 Jun 2024 17:31:56 +0100 Subject: [PATCH 4/4] update migration checking the network configuration object before running it, updated TSDoc on the migration, also added test cases for every scenario and more one that triggers when the rpc url matches on network configuration object and do not have id on provider config --- app/store/migrations/043.test.ts | 110 ++++++++++++++++++++++++++----- app/store/migrations/043.ts | 42 +++++++++--- 2 files changed, 127 insertions(+), 25 deletions(-) diff --git a/app/store/migrations/043.test.ts b/app/store/migrations/043.test.ts index a71730365b2..15c82ec9a3e 100644 --- a/app/store/migrations/043.test.ts +++ b/app/store/migrations/043.test.ts @@ -2,15 +2,14 @@ import migrate from './043'; import { merge } from 'lodash'; import { captureException } from '@sentry/react-native'; import initialRootState from '../../util/test/initial-root-state'; - +// This is a state mock with invalid networkConfigurations, derived from the state logs of an affected user const oldState = { engine: { backgroundState: { NetworkController: { networkConfigurations: { '92c0e479-6133-4a18-b1bf-fa38f654e293': { - rpcUrl: - 'https://polygon-mainnet.infura.io/v3/d039103314584a379e33c21fbe89b6cb', + rpcUrl: 'https://polygon-mainnet.infura.io/v3/12345abcd', chainId: '0x89', ticker: 'MATIC', nickname: 'Polygon Mainnet', @@ -19,8 +18,7 @@ const oldState = { }, }, '8229552c-e0ab-4337-b2c7-c572c2dc5f5a': { - rpcUrl: - 'https://optimism-mainnet.infura.io/v3/d039103314584a379e33c21fbe89b6cb', + rpcUrl: 'https://optimism-mainnet.infura.io/v3/12345abcd', chainId: '0xa', ticker: 'ETH', nickname: 'Optimism', @@ -47,8 +45,7 @@ const oldState = { }, }, '581302a2-f713-40fd-9175-e25392b49a6e': { - rpcUrl: - 'https://arbitrum-mainnet.infura.io/v3/267e54bc7b094f3f817b941097d249d8', + rpcUrl: 'https://arbitrum-mainnet.infura.io/v3/12345abcd', chainId: '0xa4b1', ticker: 'ETH', nickname: 'Arbitrum One', @@ -156,8 +153,7 @@ const expectedState = { networkConfigurations: { '92c0e479-6133-4a18-b1bf-fa38f654e293': { id: '92c0e479-6133-4a18-b1bf-fa38f654e293', - rpcUrl: - 'https://polygon-mainnet.infura.io/v3/d039103314584a379e33c21fbe89b6cb', + rpcUrl: 'https://polygon-mainnet.infura.io/v3/12345abcd', chainId: '0x89', ticker: 'MATIC', nickname: 'Polygon Mainnet', @@ -167,8 +163,7 @@ const expectedState = { }, '8229552c-e0ab-4337-b2c7-c572c2dc5f5a': { id: '8229552c-e0ab-4337-b2c7-c572c2dc5f5a', - rpcUrl: - 'https://optimism-mainnet.infura.io/v3/d039103314584a379e33c21fbe89b6cb', + rpcUrl: 'https://optimism-mainnet.infura.io/v3/12345abcd', chainId: '0xa', ticker: 'ETH', nickname: 'Optimism', @@ -198,8 +193,7 @@ const expectedState = { }, '581302a2-f713-40fd-9175-e25392b49a6e': { id: '581302a2-f713-40fd-9175-e25392b49a6e', - rpcUrl: - 'https://arbitrum-mainnet.infura.io/v3/267e54bc7b094f3f817b941097d249d8', + rpcUrl: 'https://arbitrum-mainnet.infura.io/v3/12345abcd', chainId: '0xa4b1', ticker: 'ETH', nickname: 'Arbitrum One', @@ -345,7 +339,8 @@ describe('Migration #43', () => { backgroundState: { NetworkController: null }, }, }), - errorMessage: "Migration 43: Invalid NetworkController state: 'object'", + errorMessage: + "FATAL ERROR: Migration 43: Invalid NetworkController state: 'object'", scenario: 'NetworkController is invalid', }, { @@ -357,7 +352,7 @@ describe('Migration #43', () => { }, }), errorMessage: - "Migration 43: Invalid NetworkController providerConfig state: 'object'", + "FATAL ERROR: Migration 43: Invalid NetworkController providerConfig state: 'object'", scenario: 'providerConfig is invalid', }, { @@ -371,7 +366,23 @@ describe('Migration #43', () => { }, }), errorMessage: - "Migration 43: Invalid NetworkController networkConfigurations state: 'object'", + "FATAL ERROR: Migration 43: Invalid NetworkController networkConfigurations state: 'object'", + scenario: 'networkConfigurations is invalid', + }, + { + state: merge({}, initialRootState, { + engine: { + backgroundState: { + NetworkController: { + networkConfigurations: { + '92c0e479-6133-4a18-b1bf-fa38f654e293': null, + }, + }, + }, + }, + }), + errorMessage: + "FATAL ERROR: Migration 43: Invalid NetworkController network configuration entry with id: '92c0e479-6133-4a18-b1bf-fa38f654e293', type: 'null'", scenario: 'networkConfigurations is invalid', }, ]; @@ -526,4 +537,71 @@ describe('Migration #43', () => { expect(newState).toStrictEqual(expectedState3); }); + it('should populate selected network id to network configurations if no id is found on provider config, but the rpcUrl matches the network configuration', () => { + const oldState4 = { + engine: { + backgroundState: { + NetworkController: { + networkConfigurations: { + 'a50a052d-b0e3-48f7-b1d2-e795fb52b485': { + rpcUrl: 'https://developer-access-mainnet.base.org', + chainId: '0x2105', + ticker: 'ETH', + nickname: 'Base', + rpcPrefs: { + blockExplorerUrl: 'https://basescan.org', + }, + }, + }, + providerConfig: { + type: 'rpc', + ticker: 'ETH', + chainId: '0x2105', + rpcPrefs: { + blockExplorerUrl: 'https://basescan.org', + }, + rpcUrl: 'https://developer-access-mainnet.base.org', + nickname: 'Base', + }, + }, + }, + }, + }; + const expectedState4 = { + engine: { + backgroundState: { + NetworkController: { + selectedNetworkClientId: 'a50a052d-b0e3-48f7-b1d2-e795fb52b485', + networkConfigurations: { + 'a50a052d-b0e3-48f7-b1d2-e795fb52b485': { + id: 'a50a052d-b0e3-48f7-b1d2-e795fb52b485', + rpcUrl: 'https://developer-access-mainnet.base.org', + chainId: '0x2105', + ticker: 'ETH', + nickname: 'Base', + rpcPrefs: { + blockExplorerUrl: 'https://basescan.org', + }, + }, + }, + providerConfig: { + type: 'rpc', + ticker: 'ETH', + chainId: '0x2105', + rpcPrefs: { + blockExplorerUrl: 'https://basescan.org', + }, + rpcUrl: 'https://developer-access-mainnet.base.org', + nickname: 'Base', + id: 'a50a052d-b0e3-48f7-b1d2-e795fb52b485', + }, + }, + }, + }, + }; + + const newState = migrate(oldState4); + + expect(newState).toStrictEqual(expectedState4); + }); }); diff --git a/app/store/migrations/043.ts b/app/store/migrations/043.ts index 4da5bb3b217..8ff0ba78f53 100644 --- a/app/store/migrations/043.ts +++ b/app/store/migrations/043.ts @@ -4,12 +4,18 @@ import { captureException } from '@sentry/react-native'; import { InfuraNetworkType } from '@metamask/controller-utils'; /** - * Migration to fix the "Engine does not exist" issue - * On this migration we populate selectedNetworkClientId property of Network Controller with provider config id - * or network configuration id if the rpc url matches the one on provider config - * or we default it to mainnet - * @param state Persisted Redux state - * @returns mutated state with selectNetworkClientId on Network Controller data + * Migration to fix the "No network client ID was provided" bug (#9973) and "Engine does not exist" (#9958). + * + * This migration fixes corrupted `networkConfigurations` state, which was introduced in v7.7.0 + * in migration 20. This corrupted state did not cause an error until v7.24.0. + * + * The problem was that some `networkConfigurations` entries were missing an `id` property. It has + * been restored. `selectedNetworkClientId` may have been erased as a side-effect of this invalid + * state, so it has been normalized here to match the `providerConfig` state, or reset to the + * default (main net). + * + * @param state Persisted Redux state that is potentially corrupted + * @returns Valid persisted Redux state */ export default function migrate(state: unknown) { if (!ensureValidState(state, 43)) { @@ -21,7 +27,7 @@ export default function migrate(state: unknown) { if (!isObject(networkControllerState)) { captureException( new Error( - `Migration 43: Invalid NetworkController state: '${typeof networkControllerState}'`, + `FATAL ERROR: Migration 43: Invalid NetworkController state: '${typeof networkControllerState}'`, ), ); return state; @@ -33,7 +39,25 @@ export default function migrate(state: unknown) { ) { captureException( new Error( - `Migration 43: Invalid NetworkController networkConfigurations state: '${typeof networkControllerState.networkConfigurations}'`, + `FATAL ERROR: Migration 43: Invalid NetworkController networkConfigurations state: '${typeof networkControllerState.networkConfigurations}'`, + ), + ); + return state; + } + + if ( + Object.values(networkControllerState.networkConfigurations).some( + (networkConfiguration) => !isObject(networkConfiguration), + ) + ) { + const invalidEntry = Object.entries( + networkControllerState.networkConfigurations, + ).find(([_, networkConfiguration]) => !isObject(networkConfiguration)); + captureException( + new Error( + `FATAL ERROR: Migration 43: Invalid NetworkController network configuration entry with id: '${ + invalidEntry?.[0] + }', type: '${JSON.stringify(invalidEntry?.[1])}'`, ), ); return state; @@ -45,7 +69,7 @@ export default function migrate(state: unknown) { ) { captureException( new Error( - `Migration 43: Invalid NetworkController providerConfig state: '${typeof networkControllerState.providerConfig}'`, + `FATAL ERROR: Migration 43: Invalid NetworkController providerConfig state: '${typeof networkControllerState.providerConfig}'`, ), ); return state;