diff --git a/README.md b/README.md index 54235840db..dff073e7f2 100644 --- a/README.md +++ b/README.md @@ -424,6 +424,7 @@ linkStyle default opacity:0.5 network_controller --> eth_json_rpc_provider; network_controller --> json_rpc_engine; network_controller --> messenger; + network_controller --> remote_feature_flag_controller; network_enablement_controller --> base_controller; network_enablement_controller --> controller_utils; network_enablement_controller --> messenger; diff --git a/packages/network-controller/CHANGELOG.md b/packages/network-controller/CHANGELOG.md index 14e4113c13..6c4b4eee3a 100644 --- a/packages/network-controller/CHANGELOG.md +++ b/packages/network-controller/CHANGELOG.md @@ -17,6 +17,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The default `pollingInterval` for the block tracker is now `20` seconds. - The default `retryTimeout` for the block tracker is now `20` seconds. +### Changed + +- **BREAKING:** Automatically populate `isRpcFailoverEnabled` using `RemoteFeatureFlagController` ([#9013](https://github.com/MetaMask/core/pull/9013)) + - `NetworkController.init` must now be called to fully initialize the controller. + - The constructor argument `isRpcFailoverEnabled` is no longer available. + - `RemoteFeatureFlagController:stateChange` and `RemoteFeatureFlagController:getState` are now required. + ### Fixed - Add defaults for `fetch`, `btoa` and `isOffline` in `RpcServiceOptions` ([#9000](https://github.com/MetaMask/core/pull/9000)) diff --git a/packages/network-controller/package.json b/packages/network-controller/package.json index d48d99f683..9089ebb71e 100644 --- a/packages/network-controller/package.json +++ b/packages/network-controller/package.json @@ -63,6 +63,7 @@ "@metamask/eth-query": "^4.0.0", "@metamask/json-rpc-engine": "^10.5.0", "@metamask/messenger": "^1.2.0", + "@metamask/remote-feature-flag-controller": "^4.2.2", "@metamask/rpc-errors": "^7.0.2", "@metamask/swappable-obj-proxy": "^2.3.0", "@metamask/utils": "^11.9.0", diff --git a/packages/network-controller/src/NetworkController.ts b/packages/network-controller/src/NetworkController.ts index 4cc4397e7a..2cc779140f 100644 --- a/packages/network-controller/src/NetworkController.ts +++ b/packages/network-controller/src/NetworkController.ts @@ -21,6 +21,10 @@ import { import type { PollingBlockTrackerOptions } from '@metamask/eth-block-tracker'; import EthQuery from '@metamask/eth-query'; import type { Messenger } from '@metamask/messenger'; +import { + RemoteFeatureFlagControllerGetStateAction, + RemoteFeatureFlagControllerStateChangeEvent, +} from '@metamask/remote-feature-flag-controller'; import { errorCodes } from '@metamask/rpc-errors'; import { createEventEmitterProxy, @@ -55,6 +59,7 @@ import type { NetworkControllerMethodActions, } from './NetworkController-method-action-types'; import type { RpcServiceOptionsWithDefaults } from './rpc-service/rpc-service'; +import { getIsRpcFailoverEnabled } from './selectors'; import { NetworkClientType } from './types'; import type { BlockTracker, @@ -663,7 +668,7 @@ export type NetworkControllerEvents = /** * All events that {@link NetworkController} calls internally. */ -type AllowedEvents = never; +type AllowedEvents = RemoteFeatureFlagControllerStateChangeEvent; const MESSENGER_EXPOSED_METHODS = [ 'addNetwork', @@ -714,7 +719,9 @@ export type NetworkControllerGetNetworkConfigurationByNetworkClientId = /** * All actions that {@link NetworkController} calls internally. */ -type AllowedActions = ConnectivityControllerGetStateAction; +type AllowedActions = + | ConnectivityControllerGetStateAction + | RemoteFeatureFlagControllerGetStateAction; export type NetworkControllerMessenger = Messenger< typeof controllerName, @@ -767,11 +774,6 @@ export type NetworkControllerOptions = { * An array of Hex Chain IDs representing the additional networks to be included as default. */ additionalDefaultNetworks?: AdditionalDefaultNetwork[]; - /** - * Whether or not requests sent to unavailable RPC endpoints should be - * automatically diverted to configured failover RPC endpoints. - */ - isRpcFailoverEnabled?: boolean; }; /** @@ -1295,10 +1297,7 @@ export class NetworkController extends BaseController< NetworkConfiguration >; - #isRpcFailoverEnabled: Exclude< - NetworkControllerOptions['isRpcFailoverEnabled'], - undefined - >; + #isRpcFailoverEnabled = false; /** * Constructs a NetworkController. @@ -1314,7 +1313,6 @@ export class NetworkController extends BaseController< getRpcServiceOptions, getBlockTrackerOptions, additionalDefaultNetworks, - isRpcFailoverEnabled = false, } = options; const initialState = { ...getDefaultNetworkControllerState(additionalDefaultNetworks), @@ -1357,7 +1355,6 @@ export class NetworkController extends BaseController< this.#log = log; this.#getRpcServiceOptions = getRpcServiceOptions; this.#getBlockTrackerOptions = getBlockTrackerOptions; - this.#isRpcFailoverEnabled = isRpcFailoverEnabled; this.#previouslySelectedNetworkClientId = this.state.selectedNetworkClientId; @@ -1395,6 +1392,15 @@ export class NetworkController extends BaseController< }); }, ); + + this.messenger.subscribe( + // eslint-disable-next-line no-restricted-syntax + 'RemoteFeatureFlagController:stateChange', + (isRpcFailoverEnabled) => { + this.#updateRpcFailoverEnabled(isRpcFailoverEnabled); + }, + getIsRpcFailoverEnabled, + ); } /** @@ -1626,6 +1632,14 @@ export class NetworkController extends BaseController< await this.lookupNetwork(); } + /** + * Initialize the NetworkController, updating the RPC failover feature flag. + */ + init(): void { + const state = this.messenger.call('RemoteFeatureFlagController:getState'); + this.#updateRpcFailoverEnabled(getIsRpcFailoverEnabled(state)); + } + /** * Creates proxies for accessing the global network client and its block * tracker. You must call this method in order to use diff --git a/packages/network-controller/src/selectors.ts b/packages/network-controller/src/selectors.ts new file mode 100644 index 0000000000..016c8a66b0 --- /dev/null +++ b/packages/network-controller/src/selectors.ts @@ -0,0 +1,9 @@ +import { RemoteFeatureFlagControllerState } from '@metamask/remote-feature-flag-controller'; + +export function getIsRpcFailoverEnabled( + state: RemoteFeatureFlagControllerState, +): boolean { + const walletFrameworkRpcFailoverEnabled = state.remoteFeatureFlags + .walletFrameworkRpcFailoverEnabled as boolean | undefined; + return walletFrameworkRpcFailoverEnabled ?? false; +} diff --git a/packages/network-controller/tests/NetworkController.test.ts b/packages/network-controller/tests/NetworkController.test.ts index 487f87d4b2..cb36cd2edc 100644 --- a/packages/network-controller/tests/NetworkController.test.ts +++ b/packages/network-controller/tests/NetworkController.test.ts @@ -1131,6 +1131,23 @@ describe('NetworkController', () => { describe('disableRpcFailover', () => { describe('if the controller was initialized with isRpcFailoverEnabled = true', () => { it('calls disableRpcFailover on only the network clients whose RPC endpoints have configured failover URLs', async () => { + const originalCreateAutoManagedNetworkClient = + createAutoManagedNetworkClientModule.createAutoManagedNetworkClient; + const autoManagedNetworkClients: AutoManagedNetworkClient[] = + []; + jest + .spyOn( + createAutoManagedNetworkClientModule, + 'createAutoManagedNetworkClient', + ) + .mockImplementation((...args) => { + const autoManagedNetworkClient = + originalCreateAutoManagedNetworkClient(...args); + jest.spyOn(autoManagedNetworkClient, 'disableRpcFailover'); + autoManagedNetworkClients.push(autoManagedNetworkClient); + return autoManagedNetworkClient; + }); + await withController( { isRpcFailoverEnabled: true, @@ -1171,23 +1188,6 @@ describe('NetworkController', () => { }, }, async ({ controller }) => { - const originalCreateAutoManagedNetworkClient = - createAutoManagedNetworkClientModule.createAutoManagedNetworkClient; - const autoManagedNetworkClients: AutoManagedNetworkClient[] = - []; - jest - .spyOn( - createAutoManagedNetworkClientModule, - 'createAutoManagedNetworkClient', - ) - .mockImplementation((...args) => { - const autoManagedNetworkClient = - originalCreateAutoManagedNetworkClient(...args); - jest.spyOn(autoManagedNetworkClient, 'disableRpcFailover'); - autoManagedNetworkClients.push(autoManagedNetworkClient); - return autoManagedNetworkClient; - }); - controller.disableRpcFailover(); expect(autoManagedNetworkClients).toHaveLength(3); diff --git a/packages/network-controller/tests/helpers.ts b/packages/network-controller/tests/helpers.ts index 860eb2a534..fb9f80cda5 100644 --- a/packages/network-controller/tests/helpers.ts +++ b/packages/network-controller/tests/helpers.ts @@ -89,12 +89,15 @@ export const TESTNET = { * @param options - Optional configuration. * @param options.connectivityStatus - The connectivity status to return by default. * If not provided, defaults to Online. + * @param options.isRpcFailoverEnabled - The RPC failover feature flag to return, defaults to false. * @returns The messenger. */ export function buildRootMessenger({ connectivityStatus = CONNECTIVITY_STATUSES.Online, + isRpcFailoverEnabled = false, }: { connectivityStatus?: ConnectivityStatus; + isRpcFailoverEnabled?: boolean; } = {}): RootMessenger { const rootMessenger = new Messenger< MockAnyNamespace, @@ -109,6 +112,16 @@ export function buildRootMessenger({ }), ); + rootMessenger.registerActionHandler( + 'RemoteFeatureFlagController:getState', + () => ({ + remoteFeatureFlags: { + walletFrameworkRpcFailoverEnabled: isRpcFailoverEnabled, + }, + cacheTimestamp: 0, + }), + ); + return rootMessenger; } @@ -133,7 +146,12 @@ export function buildNetworkControllerMessenger( rootMessenger.delegate({ messenger: networkControllerMessenger, - actions: ['ConnectivityController:getState'], + actions: [ + 'ConnectivityController:getState', + 'RemoteFeatureFlagController:getState', + ], + // eslint-disable-next-line no-restricted-syntax + events: ['RemoteFeatureFlagController:stateChange'], }); return networkControllerMessenger; @@ -612,7 +630,9 @@ type WithControllerCallback = ({ networkControllerMessenger: NetworkControllerMessenger; }) => Promise | ReturnValue; -type WithControllerOptions = Partial; +type WithControllerOptions = Partial & { + isRpcFailoverEnabled?: boolean; +}; type WithControllerArgs = | [WithControllerCallback] @@ -632,7 +652,8 @@ export async function withController( ...args: WithControllerArgs ): Promise { const [{ ...rest }, fn] = args.length === 2 ? args : [{}, args[0]]; - const messenger = buildRootMessenger(); + const { isRpcFailoverEnabled, ...controllerOptions } = rest; + const messenger = buildRootMessenger({ isRpcFailoverEnabled }); const networkControllerMessenger = buildNetworkControllerMessenger(messenger); const controller = new NetworkController({ messenger: networkControllerMessenger, @@ -645,8 +666,11 @@ export async function withController( btoa, isOffline: (): boolean => false, }), - ...rest, + ...controllerOptions, }); + + controller.init(); + try { return await fn({ controller, messenger, networkControllerMessenger }); } finally { diff --git a/packages/network-controller/tsconfig.build.json b/packages/network-controller/tsconfig.build.json index 0ebc69f899..5767f283bb 100644 --- a/packages/network-controller/tsconfig.build.json +++ b/packages/network-controller/tsconfig.build.json @@ -13,7 +13,8 @@ { "path": "../eth-json-rpc-middleware/tsconfig.build.json" }, { "path": "../eth-json-rpc-provider/tsconfig.build.json" }, { "path": "../json-rpc-engine/tsconfig.build.json" }, - { "path": "../messenger/tsconfig.build.json" } + { "path": "../messenger/tsconfig.build.json" }, + { "path": "../remote-feature-flag-controller/tsconfig.build.json" } ], "include": ["../../types", "./src"] } diff --git a/packages/network-controller/tsconfig.json b/packages/network-controller/tsconfig.json index 37d8a33ef5..87d6f24f68 100644 --- a/packages/network-controller/tsconfig.json +++ b/packages/network-controller/tsconfig.json @@ -12,7 +12,8 @@ { "path": "../eth-json-rpc-middleware" }, { "path": "../eth-json-rpc-provider" }, { "path": "../json-rpc-engine" }, - { "path": "../messenger" } + { "path": "../messenger" }, + { "path": "../remote-feature-flag-controller" } ], "include": ["../../types", "../../tests", "./src", "./tests"] } diff --git a/yarn.lock b/yarn.lock index 4d08c1d875..d4405d370f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7658,6 +7658,7 @@ __metadata: "@metamask/eth-query": "npm:^4.0.0" "@metamask/json-rpc-engine": "npm:^10.5.0" "@metamask/messenger": "npm:^1.2.0" + "@metamask/remote-feature-flag-controller": "npm:^4.2.2" "@metamask/rpc-errors": "npm:^7.0.2" "@metamask/swappable-obj-proxy": "npm:^2.3.0" "@metamask/utils": "npm:^11.9.0"