Skip to content

Commit 0e0ee52

Browse files
adonesky1Gudahttjiexi
authored
Fix: SelectedNetworkController should return globally selected networkClient from getProviderAndBlockTracker when domain not in state (#4063)
## Explanation The SelectedNetworkController currently constructs a proxy for any domain that has permissions. Other domains have no associated proxy, so the getProviderAndBlockTracker method would throw an error. This is problematic because we grab the network client for an origin a single time when constructing an RPC pipeline for that origin in the MetaMask extension. We don't re-create the RPC pipeline when permissions change. That means that the pipeline is setup with the wrong network client (see here for more details on this bug: MetaMask/metamask-extension#23509 ) To resolve this problem, we can instead keep network client proxies for all origins, not just those with permissions. Origins without permissions still should not have a selected network client ID state, but they can have proxies that point at the globally selected network client. That way, when the origin is granted permissions, we can redirect this proxy to the selected network client and everything works smoothly, without needing to reconstruct the RPC pipeline. This PR should be merged shortly after #4104 ## References Fixes: #4062 See: #4104 ## Changelog <!-- If you're making any consumer-facing changes, list those changes here as if you were updating a changelog, using the template below as a guide. (CATEGORY is one of BREAKING, ADDED, CHANGED, DEPRECATED, REMOVED, or FIXED. For security-related issues, follow the Security Advisory process.) Please take care to name the exact pieces of the API you've added or changed (e.g. types, interfaces, functions, or methods). If there are any breaking changes, make sure to offer a solution for consumers to follow once they upgrade to the changes. Finally, if you're only making changes to development scripts or tests, you may replace the template below with "None". --> ### `@metamask/network-controller` - **ADDED**: Add `getSelectedNetworkClient()` method that returns the provider and blockTracker for the currently selected network - **ADDED**: Add 'NetworkController:getSelectedNetworkClient' action ### `@metamask/selected-network-controller` - **BREAKING**: `SelectedNetworkController` now requires the `NetworkController:getSelectedNetworkClient` messenger action - **CHANGED**: Previously when a domain became no longer permissioned, it's network client proxy would continue to point at the networkClientId it was last set to. Now it is set to follow the globally selected network - **CHANGED**: `SelectedNetworkController. getProviderAndBlockTracker()` no longer throws an error if the `useRequestQueue` flag is false - **CHANGED**: Previously `SelectedNetworkController. getProviderAndBlockTracker()` threw an error if there was no networkClientId set for the passed domain. Now it returns a proxy that follows the globally selected network instead. ## Checklist - [ ] I've updated the test suite for new or updated code as appropriate - [ ] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [ ] I've highlighted breaking changes using the "BREAKING" category above as appropriate --------- Co-authored-by: Mark Stacey <markjstacey@gmail.com> Co-authored-by: Jiexi Luan <jiexiluan@gmail.com>
1 parent 319bcfc commit 0e0ee52

File tree

4 files changed

+265
-91
lines changed

4 files changed

+265
-91
lines changed

packages/network-controller/src/NetworkController.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,11 @@ export type NetworkControllerGetNetworkClientByIdAction = {
426426
handler: NetworkController['getNetworkClientById'];
427427
};
428428

429+
export type NetworkControllerGetSelectedNetworkClientAction = {
430+
type: `NetworkController:getSelectedNetworkClient`;
431+
handler: NetworkController['getSelectedNetworkClient'];
432+
};
433+
429434
export type NetworkControllerGetEIP1559CompatibilityAction = {
430435
type: `NetworkController:getEIP1559Compatibility`;
431436
handler: NetworkController['getEIP1559Compatibility'];
@@ -462,6 +467,7 @@ export type NetworkControllerActions =
462467
| NetworkControllerGetProviderConfigAction
463468
| NetworkControllerGetEthQueryAction
464469
| NetworkControllerGetNetworkClientByIdAction
470+
| NetworkControllerGetSelectedNetworkClientAction
465471
| NetworkControllerGetEIP1559CompatibilityAction
466472
| NetworkControllerFindNetworkClientIdByChainIdAction
467473
| NetworkControllerSetActiveNetworkAction
@@ -634,13 +640,18 @@ export class NetworkController extends BaseController<
634640
this.getNetworkConfigurationByNetworkClientId.bind(this),
635641
);
636642

643+
this.messagingSystem.registerActionHandler(
644+
`${this.name}:getSelectedNetworkClient`,
645+
this.getSelectedNetworkClient.bind(this),
646+
);
647+
637648
this.#previousProviderConfig = this.state.providerConfig;
638649
}
639650

640651
/**
641652
* Accesses the provider and block tracker for the currently selected network.
642-
*
643653
* @returns The proxy and block tracker proxies.
654+
* @deprecated This method has been replaced by `getSelectedNetworkClient` (which has a more easily used return type) and will be removed in a future release.
644655
*/
645656
getProviderAndBlockTracker(): {
646657
provider: SwappableProxy<ProxyWithAccessibleTarget<Provider>> | undefined;
@@ -654,6 +665,26 @@ export class NetworkController extends BaseController<
654665
};
655666
}
656667

668+
/**
669+
* Accesses the provider and block tracker for the currently selected network.
670+
*
671+
* @returns an object with the provider and block tracker proxies for the currently selected network.
672+
*/
673+
getSelectedNetworkClient():
674+
| {
675+
provider: SwappableProxy<ProxyWithAccessibleTarget<Provider>>;
676+
blockTracker: SwappableProxy<ProxyWithAccessibleTarget<BlockTracker>>;
677+
}
678+
| undefined {
679+
if (this.#providerProxy && this.#blockTrackerProxy) {
680+
return {
681+
provider: this.#providerProxy,
682+
blockTracker: this.#blockTrackerProxy,
683+
};
684+
}
685+
return undefined;
686+
}
687+
657688
/**
658689
* Returns all of the network clients that have been created so far, keyed by
659690
* their identifier in the network client registry. This collection represents

packages/network-controller/tests/NetworkController.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7426,6 +7426,33 @@ function lookupNetworkTests({
74267426
);
74277427
});
74287428
});
7429+
7430+
describe('getSelectedNetworkClient', () => {
7431+
it('returns the selected network provider and blockTracker proxy when initialized', async () => {
7432+
await withController(async ({ controller }) => {
7433+
const fakeProvider = buildFakeProvider();
7434+
const fakeNetworkClient = buildFakeClient(fakeProvider);
7435+
mockCreateNetworkClient().mockReturnValue(fakeNetworkClient);
7436+
await controller.initializeProvider();
7437+
const defaultNetworkClient = controller.getProviderAndBlockTracker();
7438+
7439+
const selectedNetworkClient = controller.getSelectedNetworkClient();
7440+
expect(defaultNetworkClient.provider).toBe(
7441+
selectedNetworkClient?.provider,
7442+
);
7443+
expect(defaultNetworkClient.blockTracker).toBe(
7444+
selectedNetworkClient?.blockTracker,
7445+
);
7446+
});
7447+
});
7448+
7449+
it('returns undefined when the selected network provider and blockTracker proxy are not initialized', async () => {
7450+
await withController(async ({ controller }) => {
7451+
const selectedNetworkClient = controller.getSelectedNetworkClient();
7452+
expect(selectedNetworkClient).toBeUndefined();
7453+
});
7454+
});
7455+
});
74297456
}
74307457

74317458
/**

packages/selected-network-controller/src/SelectedNetworkController.ts

Lines changed: 44 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type {
44
BlockTrackerProxy,
55
NetworkClientId,
66
NetworkControllerGetNetworkClientByIdAction,
7+
NetworkControllerGetSelectedNetworkClientAction,
78
NetworkControllerGetStateAction,
89
NetworkControllerStateChangeEvent,
910
ProviderProxy,
@@ -71,6 +72,7 @@ export type SelectedNetworkControllerActions =
7172

7273
export type AllowedActions =
7374
| NetworkControllerGetNetworkClientByIdAction
75+
| NetworkControllerGetSelectedNetworkClientAction
7476
| NetworkControllerGetStateAction
7577
| PermissionControllerHasPermissions
7678
| PermissionControllerGetSubjectsAction;
@@ -171,9 +173,7 @@ export class SelectedNetworkController extends BaseController<
171173
op === 'remove' &&
172174
this.state.domains[domain] !== undefined
173175
) {
174-
this.update(({ domains }) => {
175-
delete domains[domain];
176-
});
176+
this.#unsetNetworkClientIdForDomain(domain);
177177
}
178178
}
179179
});
@@ -222,24 +222,39 @@ export class SelectedNetworkController extends BaseController<
222222
'NetworkController:getNetworkClientById',
223223
networkClientId,
224224
);
225-
const networkProxy = this.#domainProxyMap.get(domain);
226-
if (networkProxy === undefined) {
227-
this.#domainProxyMap.set(domain, {
228-
provider: createEventEmitterProxy(networkClient.provider),
229-
blockTracker: createEventEmitterProxy(networkClient.blockTracker, {
230-
eventFilter: 'skipInternal',
231-
}),
232-
});
233-
} else {
234-
networkProxy.provider.setTarget(networkClient.provider);
235-
networkProxy.blockTracker.setTarget(networkClient.blockTracker);
236-
}
225+
const networkProxy = this.getProviderAndBlockTracker(domain);
226+
networkProxy.provider.setTarget(networkClient.provider);
227+
networkProxy.blockTracker.setTarget(networkClient.blockTracker);
237228

238229
this.update((state) => {
239230
state.domains[domain] = networkClientId;
240231
});
241232
}
242233

234+
/**
235+
* This method is used when a domain is removed from the PermissionsController.
236+
* It will remove re-point the network proxy to the globally selected network in the domainProxyMap or, if no globally selected network client is available, delete the proxy.
237+
*
238+
* @param domain - The domain for which to unset the network client ID.
239+
*/
240+
#unsetNetworkClientIdForDomain(domain: Domain) {
241+
const globallySelectedNetworkClient = this.messagingSystem.call(
242+
'NetworkController:getSelectedNetworkClient',
243+
);
244+
const networkProxy = this.#domainProxyMap.get(domain);
245+
if (networkProxy && globallySelectedNetworkClient) {
246+
networkProxy.provider.setTarget(globallySelectedNetworkClient.provider);
247+
networkProxy.blockTracker.setTarget(
248+
globallySelectedNetworkClient.blockTracker,
249+
);
250+
} else if (networkProxy) {
251+
this.#domainProxyMap.delete(domain);
252+
}
253+
this.update((state) => {
254+
delete state.domains[domain];
255+
});
256+
}
257+
243258
#domainHasPermissions(domain: Domain): boolean {
244259
return this.messagingSystem.call(
245260
'PermissionController:hasPermissions',
@@ -282,23 +297,23 @@ export class SelectedNetworkController extends BaseController<
282297
* @returns The proxy and block tracker proxies.
283298
*/
284299
getProviderAndBlockTracker(domain: Domain): NetworkProxy {
285-
if (!this.#getUseRequestQueue()) {
286-
throw new Error(
287-
'Provider and BlockTracker should be fetched from NetworkController when useRequestQueue is false',
288-
);
289-
}
290300
const networkClientId = this.state.domains[domain];
291-
if (!networkClientId) {
292-
throw new Error(
293-
'NetworkClientId has not been set for the requested domain',
294-
);
295-
}
296301
let networkProxy = this.#domainProxyMap.get(domain);
297302
if (networkProxy === undefined) {
298-
const networkClient = this.messagingSystem.call(
299-
'NetworkController:getNetworkClientById',
300-
networkClientId,
301-
);
303+
let networkClient;
304+
if (networkClientId === undefined) {
305+
networkClient = this.messagingSystem.call(
306+
'NetworkController:getSelectedNetworkClient',
307+
);
308+
if (networkClient === undefined) {
309+
throw new Error('Selected network not initialized');
310+
}
311+
} else {
312+
networkClient = this.messagingSystem.call(
313+
'NetworkController:getNetworkClientById',
314+
networkClientId,
315+
);
316+
}
302317
networkProxy = {
303318
provider: createEventEmitterProxy(networkClient.provider),
304319
blockTracker: createEventEmitterProxy(networkClient.blockTracker, {

0 commit comments

Comments
 (0)