Skip to content

Commit 0106a5f

Browse files
authored
fix: Network controller race condition after state update (#5122)
## Explanation Fixes a race condition where: - You update a network with a new endpoint. - In the `stateChange` subscription callback, call `getNetworkConfigurationByNetworkClientId` on the new endpoint. - It returns undefined. This occurs because the `network client id -> network configuration` map was being built *after* the state update, so it was not ready at the time of the `stateChange` event. The fix involves moving this map building inside the state update, so that its built by the time `stateChange` fires. But also requires deep cloning the state before building the map from it. Otherwise we get `Cannot perform 'get' on a proxy that has been revoked` when referencing the ephemeral `state` lambda. ## References <!-- Are there any issues that this pull request is tied to? Are there other links that reviewers should consult to understand these changes better? Are there client or consumer pull requests to adopt any breaking changes? For example: * Fixes #12345 * Related to #67890 --> ## 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` - **FIXED**: A race condition when calling `getNetworkConfigurationByNetworkClientId` in response to a `stateChange` event adding a new endpoint to a network. ## 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 - [ ] I've prepared draft pull requests for clients and consumer packages to resolve any breaking changes
1 parent d6a4c8f commit 0106a5f

File tree

2 files changed

+60
-15
lines changed

2 files changed

+60
-15
lines changed

packages/network-controller/src/NetworkController.ts

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import type { Hex } from '@metamask/utils';
2222
import { hasProperty, isPlainObject, isStrictHexString } from '@metamask/utils';
2323
import deepEqual from 'fast-deep-equal';
2424
import type { Draft } from 'immer';
25+
import { cloneDeep } from 'lodash';
2526
import type { Logger } from 'loglevel';
2627
import { createSelector } from 'reselect';
2728
import * as URI from 'uri-js';
@@ -1636,11 +1637,6 @@ export class NetworkController extends BaseController<
16361637
});
16371638
});
16381639

1639-
this.#networkConfigurationsByNetworkClientId =
1640-
buildNetworkConfigurationsByNetworkClientId(
1641-
this.state.networkConfigurationsByChainId,
1642-
);
1643-
16441640
this.messagingSystem.publish(
16451641
`${controllerName}:networkAdded`,
16461642
newNetworkConfiguration,
@@ -1919,11 +1915,6 @@ export class NetworkController extends BaseController<
19191915
});
19201916
}
19211917

1922-
this.#networkConfigurationsByNetworkClientId =
1923-
buildNetworkConfigurationsByNetworkClientId(
1924-
this.state.networkConfigurationsByChainId,
1925-
);
1926-
19271918
this.#unregisterNetworkClientsAsNeeded({
19281919
networkClientOperations,
19291920
autoManagedNetworkClientRegistry,
@@ -1983,11 +1974,6 @@ export class NetworkController extends BaseController<
19831974
});
19841975
});
19851976

1986-
this.#networkConfigurationsByNetworkClientId =
1987-
buildNetworkConfigurationsByNetworkClientId(
1988-
this.state.networkConfigurationsByChainId,
1989-
);
1990-
19911977
this.messagingSystem.publish(
19921978
'NetworkController:networkRemoved',
19931979
existingNetworkConfiguration,
@@ -2500,6 +2486,11 @@ export class NetworkController extends BaseController<
25002486
state.networkConfigurationsByChainId[args.networkFields.chainId] =
25012487
args.networkConfigurationToPersist;
25022488
}
2489+
2490+
this.#networkConfigurationsByNetworkClientId =
2491+
buildNetworkConfigurationsByNetworkClientId(
2492+
cloneDeep(state.networkConfigurationsByChainId),
2493+
);
25032494
}
25042495

25052496
/**

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

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11338,6 +11338,60 @@ describe('NetworkController', () => {
1133811338
});
1133911339
});
1134011340
});
11341+
11342+
it('allows calling `getNetworkConfigurationByNetworkClientId` when subscribing to state changes containing new endpoints', async () => {
11343+
const network = buildCustomNetworkConfiguration({
11344+
chainId: '0x1' as Hex,
11345+
name: 'mainnet',
11346+
nativeCurrency: 'ETH',
11347+
blockExplorerUrls: [],
11348+
defaultRpcEndpointIndex: 0,
11349+
rpcEndpoints: [
11350+
{
11351+
type: RpcEndpointType.Custom,
11352+
url: 'https://test.endpoint/1',
11353+
networkClientId: 'client1',
11354+
},
11355+
],
11356+
});
11357+
11358+
await withController(
11359+
{
11360+
state: {
11361+
selectedNetworkClientId: 'client1',
11362+
networkConfigurationsByChainId: { '0x1': network },
11363+
},
11364+
},
11365+
async ({ controller, messenger }) => {
11366+
11367+
const stateChangePromise = new Promise<NetworkConfiguration | undefined>((resolve) => {
11368+
messenger.subscribe('NetworkController:stateChange', (state) => {
11369+
const { networkClientId } =
11370+
state.networkConfigurationsByChainId['0x1'].rpcEndpoints[1];
11371+
11372+
resolve(
11373+
controller.getNetworkConfigurationByNetworkClientId(networkClientId),
11374+
);
11375+
});
11376+
});
11377+
11378+
// Add a new endpoint
11379+
await controller.updateNetwork('0x1', {
11380+
...network,
11381+
rpcEndpoints: [
11382+
...network.rpcEndpoints,
11383+
{
11384+
type: RpcEndpointType.Custom,
11385+
url: 'https://test.endpoint/2',
11386+
},
11387+
],
11388+
});
11389+
11390+
const networkConfiguration = await stateChangePromise;
11391+
expect(networkConfiguration).toBeDefined();
11392+
},
11393+
);
11394+
});
1134111395
});
1134211396

1134311397
describe('removeNetwork', () => {

0 commit comments

Comments
 (0)