Skip to content
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

Create and expose collection of network clients #1439

Merged
merged 36 commits into from
Jun 30, 2023
Merged

Conversation

mcmire
Copy link
Contributor

@mcmire mcmire commented Jun 20, 2023

Explanation

In order for users to make requests to multiple networks simultaneously, we need to phase out the concept of a single active network in the network controller in favor of a collection of networks (active or otherwise). This commit is the first step in making this happen.

To this end, this commit:

  • Adds the concept of an "auto-managed network client". This is an object that has the same exact interface as a network client — that is, it exposes a provider and block tracker — except that the network client doesn't spring into existence until an attempt is made to use the provider or block tracker in some way.
  • Adds a registry to hold auto-managed network clients. This registry will be prepopulated with networks we've built in to MetaMask as well as any custom networks that already exist in state under networkConfigurations.
  • Adds a new method getNetworkClients which returns the contents of the registry.
  • Update the logic that is executed when initializeProvider, setProviderType, setActiveNetwork, or resetConnection is called so that the "active network client" — i.e. the targets of the provider and block tracker proxy — is merely set based on some existing network client in the registry instead of being recreated each time.
  • Updates upsertNetworkConfiguration and removeNetworkConfiguration to either a) create a new network client in the event that a new network configuration is added and add it to the network client registry or b) destroy and remove an existing network client from the registry then create a new one in the event that the chain ID for an existing network configuration is updated.

References

Fixes #1045.

Changelog

@metamask/network-controller

  • ADDED: Add getNetworkClientsById method
    • This returns a registry of built-in and custom networks that the network controller is cognizant of, allowing consumers to access multiple networks simultaneously if desired
  • CHANGED: Update the controller so that a network client is no longer recreated each time the network is initialized or switched; rather, a network client which has been created ahead of time will be utilized
  • CHANGED: Update upsertNetworkConfiguration to keep the network client registry up to date with changes to the set of network configurations
    • If a new network configuration is added, the information in it will be used to create and register a new network client
    • If an existing network configuration is updated, its information will be used to recreate the client for the corresponding 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

@mcmire mcmire marked this pull request as ready for review June 21, 2023 20:26
@mcmire mcmire requested a review from a team as a code owner June 21, 2023 20:26
@mcmire mcmire force-pushed the expose-network-clients branch 4 times, most recently from 38df17a to 6d11b2f Compare June 21, 2023 22:01
In order for users to make requests to multiple networks simultaneously,
we need to phase out the concept of a single active network in the
network controller in favor of a collection of networks (active or
otherwise). This commit is the first step in making this happen.

To this end, this commit:

- Adds the concept of an "auto-managed network client". This is an
  object that has the same exact interface as a network client — that
  is, it exposes a provider and block tracker — except that the network
  client doesn't spring into existence until an attempt is made to use
  the provider or block tracker in some way. This prevents network
  requests from occurring prematurely.
- Adds a registry to hold auto-managed network clients. This registry
  will be prepopulated with networks we've built in to MetaMask as well
  as any custom networks that already exist in state under
  `networkConfigurations`.
- Adds a new method `getNetworkClients` which returns the contents of
  the registry.
- Update the logic that is executed when `initializeProvider`,
  `setProviderType`, `setActiveNetwork`, or `resetConnection` is called
  so that the "active network client" — i.e. the targets of the provider
  and block tracker proxy — is merely set based on some existing network
  client in the registry instead of being recreated each time.
- Updates `upsertNetworkConfiguration` and `removeNetworkConfiguration`
  to either a) create a new network client in the event that a new
  network configuration is added and add it to the network client
  registry or b) destroy and remove an existing network client from the
  registry then create a new one in the event that the chain ID for an
  existing network configuration is updated.
packages/network-controller/src/NetworkController.ts Outdated Show resolved Hide resolved
@@ -353,42 +420,57 @@ export class NetworkController extends BaseControllerV2<
this.#previousProviderConfig = this.state.providerConfig;
}

#configureProvider(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This method effectively turned into #createAutoManagedNetworkClientRegistry.

this.messagingSystem.publish('NetworkController:networkDidChange');
await this.lookupNetwork();
}

#registerProvider() {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I consolidated this method into #selectNetwork.

}
}

#setupInfuraProvider(type: InfuraNetworkType) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The code in this and the next method is now split up into two methods. The code to create the network client is now in #createAutoManagedNetworkClientRegistry and the code to update the provider is now in #selectNetwork.

): Promise<string> {
assertIsStrictHexString(chainId);
const sanitizedNetworkConfiguration = (
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We were technically doing this before by destructuring the object that this method takes, but that approach meant that properties of the network configuration object that were not technically provided ended up as undefined when put into networkConfigurations. The code here is essentially equivalent to Lodash's pick function.

Copy link
Member

Choose a reason for hiding this comment

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

Makes sense! Potentially simpler to use the argument as-is and throw if extra properties are there (safer too, as we'd be informed about these properties being ignored rather than them being silently dropped). But that would be better as a separate PR, if we did want to go that route.

/**
* Handles mocking JSON-requests sent to the network.
*/
class MockedNetwork {
Copy link
Contributor Author

@mcmire mcmire Jun 21, 2023

Choose a reason for hiding this comment

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

This may look familiar; there have been a couple of different variations on this theme elsewhere. That said, it doesn't do anything by default, so it's simpler than what we've done before.

packages/network-controller/src/NetworkController.ts Outdated Show resolved Hide resolved
packages/network-controller/src/NetworkController.ts Outdated Show resolved Hide resolved
packages/network-controller/src/NetworkController.ts Outdated Show resolved Hide resolved
*
* @returns The list of known network clients.
*/
getNetworkClients(): AutoManagedNetworkClient<NetworkClientConfiguration>[] {
Copy link
Member

Choose a reason for hiding this comment

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

Hmm, do you think a list is the right data structure to use here? I'm just wondering how this is likely to be used. Maybe having them keyed by something (e.g. network name for built-in, or id for custom) would be better.

Presumably, in most places where this is called, the caller is going to be looking for a specific network client. Keying it by whatever unique identifier the caller is likely to have would probably make this easier to use.

Copy link
Contributor Author

@mcmire mcmire Jun 28, 2023

Choose a reason for hiding this comment

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

Right. The first version of this method was called getNetworkClientsById and returned an object keyed by an identifier, which for Infura networks would look like infura||<network> and for custom networks would look like custom||<chainId>||<lowercased rpcUrl> or custom||<network configuration ID>, depending on where it came from 1.

Ultimately I wasn't sure how we will end up using this collection and figured that once we had more information we could tweak this. So I went with an array for now. But, an object would definitely be more versatile. I'll play around with this some more.

Footnotes

  1. Custom networks aren't always associated with a network configuration ID, so we can't always use that in the identifier (because the network controller can be instantiated with a providerConfig that has nothing to do with a built-in network or a network in networkConfigurations).

Copy link
Member

Choose a reason for hiding this comment

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

Custom networks aren't always associated with a network configuration ID

They are if you consider just how extension and mobile are using this controller. We can consider it a breaking change of this API, but "supporting custom networks specified via provider config that don't correspond with network configurations" is not a feature we're supporting intentionally. If anything it's a bug.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Okay, I've changed this method to return an object rather than an array: 96c4229

This reverts commit edd4a8bb1940558e5f0ebda743c92eb8b41fa8e2.
- Extract code that builds a network client ID
- Refactor `#createAutoManagedNetworkClientRegistry` to avoid loop
- Use full network client IDs in registry so that we can change
  `getNetworkClients()`
networkConfigurations: {
testNetworkConfiguration: {
id: 'testNetworkConfiguration',
describe('if a provider has not been set', () => {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The functionality tested here already exists, but as I was updating the JSDocs for rollbackToPreviousProvider, I noticed that they referred to this behavior. I wanted to ensure that I wasn't breaking anything, so I backfilled them. I also grouped the existing tests under a describe — that's what's causing the large diff here. I didn't change any of the existing tests, though, so if you remove this describe and then ungroup the tests within the next one, then you shouldn't see any differences in the diff.

@mcmire
Copy link
Contributor Author

mcmire commented Jun 29, 2023

@Gudahtt I believe I've addressed all of your comments above.

if (typeof infuraNetworkOrProviderConfig === 'string') {
return `infura||${infuraNetworkOrProviderConfig}` as const;
}
return `infura||${infuraNetworkOrProviderConfig.type}` as const;
Copy link
Member

Choose a reason for hiding this comment

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

How important is it that we use a composite ID?

I'm wondering because I'm hoping that we'll eventually migrate the built-in networks to be more similar to the custom networks (one type of network client, one middleware stack, etc., see #1279), at which point we presumably won't have a need for this custom|| prefix on each ID.

And until then, we don't have to worry about ID conflicts. The UUIDs we create for custom networks will never conflict with the short built-in network names.

Perhaps dropping the composite ID now would save us the effort of removing it in a migration later?

Copy link
Member

Choose a reason for hiding this comment

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

It'd also make it a bit simpler to access network clients in cases where we don't already have the network client ID. We could do soemthing like networkClients[providerConfig.type || providerConfig.id] , no need to construct the ID each time.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We don't really need the prefix to be able to tell what type of network client it is, that's true. One benefit to using a prefix that I can think of is that it allows the type of a built-in network client ID to be clearly different from the type of a custom network client ID — and actually, it allows a type to be assigned to the network client ID at all. If we drop the prefix, then the type of a custom network client ID is just string. At that point, any kind of network client ID itself, at least from a consumer perspective, will end up being string, whether Infura or not.

Maybe that's fine though. Network configuration IDs are just strings and we're okay with that. I've made this change here: 9e67774

Copy link
Member

Choose a reason for hiding this comment

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

Right... yeah this does make type narrowing more challenging. It's still possible for us to narrow from string to InfuraNetworkType though at least. We'd need to compare with each entry rather than just look at the prefix. This seems OK though.

Copy link
Member

@Gudahtt Gudahtt left a comment

Choose a reason for hiding this comment

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

Looks good! All that's left are nits, plus that one question about the ID. I'd like to at least hear your thoughts about the ID before proceeding but otherwise I'm ready to approve.

Copy link
Member

@Gudahtt Gudahtt left a comment

Choose a reason for hiding this comment

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

LGTM!

@mcmire mcmire merged commit f70430e into main Jun 30, 2023
@mcmire mcmire deleted the expose-network-clients branch June 30, 2023 15:25
@@ -296,8 +530,12 @@ export class NetworkController extends BaseControllerV2<

#providerProxy: ProviderProxy | undefined;

#provider: ProxyWithAccessibleTarget<Provider> | undefined;
Copy link
Contributor

Choose a reason for hiding this comment

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

What is this doing? It looks like we set it but never use it? And its private so it couldn't used externally? cc @mcmire

MajorLift pushed a commit that referenced this pull request Oct 11, 2023
Create and expose collection of network clients

In order for users to make requests to multiple networks simultaneously,
we need to phase out the concept of a single active network in the
network controller in favor of a collection of networks (active or
otherwise). This commit is the first step in making this happen.

To this end, this commit:

- Adds the concept of an "auto-managed network client". This is an
  object that has the same exact interface as a network client — that
  is, it exposes a provider and block tracker — except that the network
  client doesn't spring into existence until an attempt is made to use
  the provider or block tracker in some way.
- Adds a registry to hold auto-managed network clients. This registry
  will be prepopulated with networks we've built in to MetaMask as well
  as any custom networks that already exist in state under
  `networkConfigurations`.
- Adds a new method `getNetworkClientsById` which returns the contents
  of the registry, keyed by identifier.
- Update the logic that is executed when `initializeProvider`,
  `setProviderType`, `setActiveNetwork`, or `resetConnection` is called
  so that the "active network client" — i.e. the targets of the provider
  and block tracker proxy — is merely set based on some existing network
  client in the registry instead of being recreated each time.
- Updates `upsertNetworkConfiguration` and `removeNetworkConfiguration`
  to either a) create a new network client in the event that a new
  network configuration is added and add it to the network client
  registry or b) destroy and remove an existing network client from the
  registry then create a new one in the event that the chain ID for an
  existing network configuration is updated.

Co-authored-by: Mark Stacey <[email protected]>
MajorLift pushed a commit that referenced this pull request Oct 11, 2023
Create and expose collection of network clients

In order for users to make requests to multiple networks simultaneously,
we need to phase out the concept of a single active network in the
network controller in favor of a collection of networks (active or
otherwise). This commit is the first step in making this happen.

To this end, this commit:

- Adds the concept of an "auto-managed network client". This is an
  object that has the same exact interface as a network client — that
  is, it exposes a provider and block tracker — except that the network
  client doesn't spring into existence until an attempt is made to use
  the provider or block tracker in some way.
- Adds a registry to hold auto-managed network clients. This registry
  will be prepopulated with networks we've built in to MetaMask as well
  as any custom networks that already exist in state under
  `networkConfigurations`.
- Adds a new method `getNetworkClientsById` which returns the contents
  of the registry, keyed by identifier.
- Update the logic that is executed when `initializeProvider`,
  `setProviderType`, `setActiveNetwork`, or `resetConnection` is called
  so that the "active network client" — i.e. the targets of the provider
  and block tracker proxy — is merely set based on some existing network
  client in the registry instead of being recreated each time.
- Updates `upsertNetworkConfiguration` and `removeNetworkConfiguration`
  to either a) create a new network client in the event that a new
  network configuration is added and add it to the network client
  registry or b) destroy and remove an existing network client from the
  registry then create a new one in the event that the chain ID for an
  existing network configuration is updated.

Co-authored-by: Mark Stacey <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Expose collection of network clients
3 participants