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

Expose collection of network clients #1045

Closed
Gudahtt opened this issue Jan 9, 2023 · 3 comments · Fixed by #1439
Closed

Expose collection of network clients #1045

Gudahtt opened this issue Jan 9, 2023 · 3 comments · Fixed by #1439
Assignees
Labels

Comments

@Gudahtt
Copy link
Member

Gudahtt commented Jan 9, 2023

(For more context, see the epic: #1135, and also for a longer explanation, see #1045 (comment))

To enable support for multiple simultaneous connections, the network controller should store and provide access to a collection of network clients, one for each configured network. (A network client is a pair of provider and block tracker; a configured network is either a custom RPC network or a built-in Infura network.) The current way of accessing a single provider and block tracker should be deprecated.

Implementation tasks:

  • Revamp the concept of a network client, and create the concepts of a provider proxy and block tracker proxy1 (which cause the provider and block tracker to "spring to life" upon first access)
  • Add customNetworkClients and infuraNetworkClients private properties (customNetworkClients is keyed by network configuration ID, infuraNetworkClients is keyed by Infura network name)
  • Update the constructor to use a predetermined list of networks to populate infuraNetworkClients with the revamped network clients (but uninitialized at first)
  • Update the constructor to use networkConfigurations in state to populate customNetworkClients with the revamped network client objects (but uninitialized at first)
  • Replace private providerProxy and blockTrackerProxy properties with networkClientProxy
  • Update setProviderType to set/update networkClientProxy to the network client matched by the given Infura network name
  • Update setActiveNetwork to set/update networkClientProxy to the network client matched by the given network configuration ID
  • Update initializeProvider to use providerConfig to create an appropriate network client and then set networkClientProxy
  • Add method getNetworkClients, which returns a combination of customNetworkClients and infuraNetworkClients (either an object keyed by network client type + network client identifier or just an array)

Footnotes

  1. This proxy is different from the current concept of provider and block tracker proxy.

@Gudahtt
Copy link
Member Author

Gudahtt commented Jan 10, 2023

At the moment, we construct the network client on-demand when switching to a network. In order to return a collection of network clients for each configured network, we'll need to start creating them ahead of time instead.

We still want the behavior to be functionally identical for mobile and extension for now though, meaning that we'll have to ensure that constructing the network clients ahead of time has no side-effects. We can continue to perform certain initialization actions upon switch or upon first use of a client, similar to how we do today (e.g. send net_version to verify that we can connect to the network, check EIP1559 compatibility, etc.).

@Gudahtt
Copy link
Member Author

Gudahtt commented Jan 10, 2023

A network client is "in-use" when something is listening to its block tracker. As such, we don't have any "active"/"connected" state to keep track of for each client; instead we can use the blockTracker.isRunning() method to check if a network is running or not.

We may want to add some additional "active" state in the future if this becomes challenging to work with, but I suspect we can get away with leaving things as-is for now. This is something to consider for future API improvements at least.

@mcmire
Copy link
Contributor

mcmire commented Jun 9, 2023

@Gudahtt and I had a meeting about this today. Here is my interpretation of that meeting:


Currently, when MetaMask is initialized with a certain network, or when a user switches to a different network, the network controller constructs a new network client (a combination of a provider and a block tracker) on the fly for the configured network. This may be either a custom network (one that the user adds via the Networks screen) or a built-in Infura network (one that users do not need to add to the client in order to connect to). However, MetaMask — and thus a dapp — may only be connected to a single network at a time; if it wishes to access a different network, it must switch the network, which entails constructing a new network client.

To enable access to multiple networks, the network controller must construct and store multiple network clients for both custom and built-in networks. As we are moving to a multichain world in stages, the controller will retain the concept of an "active" network, but when that network is changed, it will also hold a "connection" to other networks.

How will this work implementation-wise?

First, in order to store and look up the network client for a network, the network controller must have a way of identifying that network. Custom networks already have an identifier, as they are implemented via network configuration objects, which have an id. Built-in networks also technically already have an identifier: the Infura network name. Therefore, when the network controller stores a network client, it can file it under the identifier for the network it belongs to. In other words, we might want to have two collections of network clients, one called customNetworkClients and another called infuraNetworkClients; and each collection would be an object keyed by the network client's identifier (for the type of network client that object holds).

And when does the network controller construct a network client? For custom networks, this can happen when the user adds the network. But built-in networks require more consideration. Since we know these type of networks up front, we might want to construct them up front, in an initialization step. However, one important step we perform once we have a provider for a network is to check the network's status by performing a couple of network requests. We would have to do this for each built-in network. If that step occurs before MetaMask starts, that would incur a performance cost we don't want.

The solution is to make the network client construction lazy: only do it when the network is accessed for the first time. This would apply to not only built-in networks but also custom networks.

However, that creates a catch-22, where the network client (either the provider or block tracker) wouldn't exist until the network is accessed, yet to access the network, users would need a network client. How do we solve this?

If a network client hasn't been constructed yet, the network controller can present the consumer with proxy versions of the provider and block tracker. (This would be different from the current concept of the provider proxy and block tracker proxy.)

The provider proxy would respond to the same interface as a non-proxy, but initially its target would be empty. Whenever a consumer made a request through the provider for the first time, the controller would construct the "real" network client behind the scenes and point the proxy to it; then once it confirmed the status of the network, it would resolve the request. Subsequent requests through the provider would operate as normal.

The block tracker would be similar, at least somewhat. When a consumer called a method on the block tracker, the controller would construct a network client, then forward the method onto the real block tracker. However, the block tracker differs slightly from the provider because consumers can not only call methods, they can also listen for events. In that case, the controller would add the event listener from the consumer to the real block tracker after the network client was fully constructed. This is fine from an asynchronousness perspective because event listeners run out-of-band anyway.

Lastly, there is the item of what to do about the "active" network. Currently, the network controller only ever exposes the network client for the currently selected network to consumers. It does this by keeping proxy objects for the provider and block tracker and updating them as the network changes. The method which grants consumers access to these proxies is called getProviderAndBlockTracker. We want to keep this method — at least for the time being — but also provide access to network clients for all of the configured networks. To do this, we can add another method, perhaps called getNetworkClients. This would be a combination of customNetworkClients and infuraNetworkClients.

That would work for now, and we could follow this up in the future with a couple of improvements:

  • Detect network clients that haven't been used in a while and automatically destroy them in order to reliably reduce unnecessary network traffic
  • Update client platforms to check the status of the network when switching to a new network or when network conditions change (the user switches from wifi to data, etc.)

I will update the PR description with a summary of this information.

I have also downgraded the estimate to a 5.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants