-
-
Notifications
You must be signed in to change notification settings - Fork 187
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: NetworkController changes to support Network Syncing #4939
base: main
Are you sure you want to change the base?
Changes from all commits
ad4ec39
6e65961
39b89c6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -212,8 +212,11 @@ export type NetworkConfiguration = { | |
* Custom RPC endpoints do not need a `networkClientId` property because it is | ||
* assumed that they have not already been added and therefore network clients | ||
* do not exist for them yet (and hence IDs need to be generated). | ||
* | ||
* However Custom RPC endpoints, that are synchronized between devices, | ||
* can contain a `networkClientId` set on both devices. | ||
*/ | ||
export type AddNetworkCustomRpcEndpointFields = Omit< | ||
export type AddNetworkCustomRpcEndpointFields = Partialize< | ||
CustomRpcEndpoint, | ||
'networkClientId' | ||
>; | ||
|
@@ -473,6 +476,11 @@ export type NetworkControllerGetNetworkConfigurationByNetworkClientId = { | |
handler: NetworkController['getNetworkConfigurationByNetworkClientId']; | ||
}; | ||
|
||
export type NetworkControllerDangerouslySetNetworkConfigurationAction = { | ||
type: 'NetworkController:dangerouslySetNetworkConfiguration'; | ||
handler: NetworkController['dangerouslySetNetworkConfiguration']; | ||
}; | ||
|
||
export type NetworkControllerActions = | ||
| NetworkControllerGetStateAction | ||
| NetworkControllerGetEthQueryAction | ||
|
@@ -483,7 +491,8 @@ export type NetworkControllerActions = | |
| NetworkControllerSetActiveNetworkAction | ||
| NetworkControllerSetProviderTypeAction | ||
| NetworkControllerGetNetworkConfigurationByChainId | ||
| NetworkControllerGetNetworkConfigurationByNetworkClientId; | ||
| NetworkControllerGetNetworkConfigurationByNetworkClientId | ||
| NetworkControllerDangerouslySetNetworkConfigurationAction; | ||
|
||
export type NetworkControllerMessenger = RestrictedControllerMessenger< | ||
typeof controllerName, | ||
|
@@ -954,6 +963,13 @@ export class NetworkController extends BaseController< | |
`${this.name}:getSelectedNetworkClient`, | ||
this.getSelectedNetworkClient.bind(this), | ||
); | ||
|
||
this.messagingSystem.registerActionHandler( | ||
// TODO: Either fix this lint violation or explain why it's necessary to ignore. | ||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions | ||
`${this.name}:dangerouslySetNetworkConfiguration`, | ||
this.dangerouslySetNetworkConfiguration.bind(this), | ||
); | ||
} | ||
|
||
/** | ||
|
@@ -1557,7 +1573,8 @@ export class NetworkController extends BaseController< | |
defaultOrCustomRpcEndpointFields.type === RpcEndpointType.Custom | ||
? { | ||
...defaultOrCustomRpcEndpointFields, | ||
networkClientId: uuidV4(), | ||
networkClientId: | ||
defaultOrCustomRpcEndpointFields.networkClientId ?? uuidV4(), | ||
} | ||
: defaultOrCustomRpcEndpointFields; | ||
return { | ||
|
@@ -1941,6 +1958,129 @@ export class NetworkController extends BaseController< | |
); | ||
} | ||
|
||
/** | ||
* This is used to override an existing network configuration. | ||
* This is only meant for internal use only and not to be exposed via the UI. | ||
* It is used as part of "Network Syncing", to sync networks, RPCs and block explorers cross devices. | ||
* | ||
* This will subsequently update the network client registry; state.networksMetadata, and state.selectedNetworkClientId | ||
* @param networkConfiguration - the network configuration to override | ||
*/ | ||
async dangerouslySetNetworkConfiguration( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep, I understand that this is a lot of logic here. I've ensured that we correctly update controller state; as well as internal data structures used inside the controller. Fundamentally we need this method as we are unable to sync/override networks due to the random ( There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The name for this is a bit of a throwback to Reacts |
||
networkConfiguration: NetworkConfiguration, | ||
) { | ||
const prevNetworkConfig: NetworkConfiguration | undefined = | ||
networkConfiguration.chainId in this.state.networkConfigurationsByChainId | ||
? this.state.networkConfigurationsByChainId[ | ||
networkConfiguration.chainId | ||
] | ||
: undefined; | ||
|
||
if (!prevNetworkConfig) { | ||
// We only want to perform overrides, not add new network configurations | ||
return; | ||
} | ||
|
||
// Update Registry (remove old and add new) | ||
const updateRegistry = () => { | ||
// Unregister old networks we want to override | ||
const autoManagedNetworkClientRegistry = | ||
this.#ensureAutoManagedNetworkClientRegistryPopulated(); | ||
const networkClientRemoveOperations = prevNetworkConfig.rpcEndpoints.map( | ||
(rpcEndpoint) => { | ||
return { | ||
type: 'remove' as const, | ||
rpcEndpoint, | ||
}; | ||
}, | ||
); | ||
this.#unregisterNetworkClientsAsNeeded({ | ||
networkClientOperations: networkClientRemoveOperations, | ||
autoManagedNetworkClientRegistry, | ||
}); | ||
|
||
// Register new networks we want to override | ||
const networkClientAddOperations = networkConfiguration.rpcEndpoints.map( | ||
(rpcEndpoint) => { | ||
return { | ||
type: 'add' as const, | ||
rpcEndpoint, | ||
}; | ||
}, | ||
); | ||
this.#registerNetworkClientsAsNeeded({ | ||
networkFields: networkConfiguration, | ||
networkClientOperations: networkClientAddOperations, | ||
autoManagedNetworkClientRegistry, | ||
}); | ||
}; | ||
|
||
// Replace the networkConfiguration with our new networkConfiguration | ||
// This is a full replace (no merging) | ||
const replaceNetworkConfiguration = () => { | ||
// Update State | ||
this.update((state) => { | ||
state.networkConfigurationsByChainId[networkConfiguration.chainId] = | ||
networkConfiguration; | ||
}); | ||
|
||
// Update Cache | ||
this.#networkConfigurationsByNetworkClientId = | ||
buildNetworkConfigurationsByNetworkClientId( | ||
this.state.networkConfigurationsByChainId, | ||
); | ||
}; | ||
|
||
// Updates the NetworksMetadata State | ||
const updateNetworksMetadata = async () => { | ||
// Remove old metadata state | ||
this.update((state) => { | ||
prevNetworkConfig.rpcEndpoints.forEach((r) => { | ||
if (state.networksMetadata?.[r.networkClientId]) { | ||
delete state.networksMetadata[r.networkClientId]; | ||
} | ||
}); | ||
}); | ||
|
||
// Add new metadata state | ||
for (const r of networkConfiguration.rpcEndpoints) { | ||
await this.lookupNetwork(r.networkClientId); | ||
} | ||
}; | ||
|
||
// Update selectedNetworkId State | ||
// Will try to keep the same OR will select a new RPC from new network OR any network (edge case) | ||
const updateSelectedNetworkId = async () => { | ||
const selectedClientId = this.state.selectedNetworkClientId; | ||
const wasClientIdReplaced = prevNetworkConfig.rpcEndpoints.some( | ||
(r) => r.networkClientId === selectedClientId, | ||
); | ||
const doesExistInNewNetwork = networkConfiguration.rpcEndpoints.some( | ||
(r) => r.networkClientId === selectedClientId, | ||
); | ||
|
||
const shouldUpdateSelectedNetworkId = | ||
wasClientIdReplaced && !doesExistInNewNetwork; | ||
if (shouldUpdateSelectedNetworkId) { | ||
// Update the clientId to "something" that exists | ||
const newRPCClientId = networkConfiguration.rpcEndpoints.find( | ||
(r) => r.networkClientId in this.state.networksMetadata, | ||
)?.networkClientId; | ||
const anyRPCClientId = Object.keys(this.state.networksMetadata)[0]; | ||
/* istanbul ignore next: anyRPCClientId and selectedClientId are fallbacks and should be impossible to reach */ | ||
const newlySelectedNetwork = | ||
newRPCClientId ?? anyRPCClientId ?? selectedClientId; | ||
await this.#refreshNetwork(newlySelectedNetwork); | ||
} | ||
}; | ||
|
||
// Execute Set Network Config | ||
updateRegistry(); | ||
replaceNetworkConfiguration(); | ||
await updateNetworksMetadata(); | ||
await updateSelectedNetworkId(); | ||
} | ||
|
||
/** | ||
* Assuming that the network has been previously switched, switches to this | ||
* new network. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For custom RPCs, the
networkClientId
is a random UUID.To make it easier for syncing, if a device is adding new networks from remote, we can reuse the UUID.
This was there is less chance of having some weird state where 2 devices use the same networks + RPCs, but they are different UUIDs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is mostly optional (to keep uuid eventual consistency) across devices.