Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
7 changes: 7 additions & 0 deletions packages/network-controller/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
1 change: 1 addition & 0 deletions packages/network-controller/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
40 changes: 27 additions & 13 deletions packages/network-controller/src/NetworkController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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;
};

/**
Expand Down Expand Up @@ -1295,10 +1297,7 @@ export class NetworkController extends BaseController<
NetworkConfiguration
>;

#isRpcFailoverEnabled: Exclude<
NetworkControllerOptions['isRpcFailoverEnabled'],
undefined
>;
#isRpcFailoverEnabled = false;

/**
* Constructs a NetworkController.
Expand All @@ -1314,7 +1313,6 @@ export class NetworkController extends BaseController<
getRpcServiceOptions,
getBlockTrackerOptions,
additionalDefaultNetworks,
isRpcFailoverEnabled = false,
} = options;
const initialState = {
...getDefaultNetworkControllerState(additionalDefaultNetworks),
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -1395,6 +1392,15 @@ export class NetworkController extends BaseController<
});
},
);

this.messenger.subscribe(
// eslint-disable-next-line no-restricted-syntax
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the restricted syntax here? 🤔

Copy link
Copy Markdown
Member Author

@FrederikBolding FrederikBolding Jun 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have a special rule for stateChanged / stateChange. But not many controllers have exported types for it yet, so I can't really use the new format here.

'RemoteFeatureFlagController:stateChange',
(isRpcFailoverEnabled) => {
this.#updateRpcFailoverEnabled(isRpcFailoverEnabled);
},
getIsRpcFailoverEnabled,
);
}

/**
Expand Down Expand Up @@ -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));
Comment thread
Mrtenz marked this conversation as resolved.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Init eagerly builds all clients

Medium Severity

init routes through #updateRpcFailoverEnabled, which populates the auto-managed network client registry whenever the remote failover flag is true. The old constructor only stored the boolean and deferred client creation until the network was first used, so startup can now create every network client early when the flag is on.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 5ffe1c5. Configure here.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is done at init time anyways via initializeProvider in both clients. Should not matter.

}

/**
* Creates proxies for accessing the global network client and its block
* tracker. You must call this method in order to use
Expand Down
9 changes: 9 additions & 0 deletions packages/network-controller/src/selectors.ts
Original file line number Diff line number Diff line change
@@ -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;
}
34 changes: 17 additions & 17 deletions packages/network-controller/tests/NetworkController.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This spy needed to be applied earlier to ensure it caught the first call to createAutoManagedNetworkClient

createAutoManagedNetworkClientModule.createAutoManagedNetworkClient;
const autoManagedNetworkClients: AutoManagedNetworkClient<NetworkClientConfiguration>[] =
[];
jest
.spyOn(
createAutoManagedNetworkClientModule,
'createAutoManagedNetworkClient',
)
.mockImplementation((...args) => {
const autoManagedNetworkClient =
originalCreateAutoManagedNetworkClient(...args);
jest.spyOn(autoManagedNetworkClient, 'disableRpcFailover');
autoManagedNetworkClients.push(autoManagedNetworkClient);
return autoManagedNetworkClient;
});

await withController(
{
isRpcFailoverEnabled: true,
Expand Down Expand Up @@ -1171,23 +1188,6 @@ describe('NetworkController', () => {
},
},
async ({ controller }) => {
const originalCreateAutoManagedNetworkClient =
createAutoManagedNetworkClientModule.createAutoManagedNetworkClient;
const autoManagedNetworkClients: AutoManagedNetworkClient<NetworkClientConfiguration>[] =
[];
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);
Expand Down
32 changes: 28 additions & 4 deletions packages/network-controller/tests/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -109,6 +112,16 @@ export function buildRootMessenger({
}),
);

rootMessenger.registerActionHandler(
'RemoteFeatureFlagController:getState',
() => ({
remoteFeatureFlags: {
walletFrameworkRpcFailoverEnabled: isRpcFailoverEnabled,
},
cacheTimestamp: 0,
}),
);

return rootMessenger;
}

Expand All @@ -133,7 +146,12 @@ export function buildNetworkControllerMessenger(

rootMessenger.delegate({
messenger: networkControllerMessenger,
actions: ['ConnectivityController:getState'],
actions: [
'ConnectivityController:getState',
'RemoteFeatureFlagController:getState',
],
Comment thread
cursor[bot] marked this conversation as resolved.
// eslint-disable-next-line no-restricted-syntax
events: ['RemoteFeatureFlagController:stateChange'],
});

return networkControllerMessenger;
Expand Down Expand Up @@ -612,7 +630,9 @@ type WithControllerCallback<ReturnValue> = ({
networkControllerMessenger: NetworkControllerMessenger;
}) => Promise<ReturnValue> | ReturnValue;

type WithControllerOptions = Partial<NetworkControllerOptions>;
type WithControllerOptions = Partial<NetworkControllerOptions> & {
isRpcFailoverEnabled?: boolean;
};

type WithControllerArgs<ReturnValue> =
| [WithControllerCallback<ReturnValue>]
Expand All @@ -632,7 +652,8 @@ export async function withController<ReturnValue>(
...args: WithControllerArgs<ReturnValue>
): Promise<ReturnValue> {
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,
Expand All @@ -645,8 +666,11 @@ export async function withController<ReturnValue>(
btoa,
isOffline: (): boolean => false,
}),
...rest,
...controllerOptions,
});

controller.init();

try {
return await fn({ controller, messenger, networkControllerMessenger });
} finally {
Expand Down
3 changes: 2 additions & 1 deletion packages/network-controller/tsconfig.build.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
}
3 changes: 2 additions & 1 deletion packages/network-controller/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
}
1 change: 1 addition & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Loading