Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

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

Discussion: Provider will fail if the backend network changes except for "any" #866

Closed
ricmoo opened this issue Jun 4, 2020 · 1 comment
Labels
META An Issue about Issues

Comments

@ricmoo
Copy link
Member

ricmoo commented Jun 4, 2020

This is the beginning for an issue I think will seem confusing to some at first, but is the culmination of a lot of thought, discussion and experimentation. :)

The related issues are: #495 #589 #861

tl;dr: This only affects backends that can change their network, such as MetaMask or using your own Geth instance; to get the legacy functionality, pass the string "any" as the network parameter, but please be aware that there are many edge cases your dapp should be handling that you may not be. Not using the "any" network might help flush out parts of your dapp which were previously unsafe in cases you hadn't even thought of. :)

Provider calls result in NETWORK_ERROR

Basically, when a Provider is created, it will self-detect its network (or can have its network explicitly specified in the constructor) and whenever executing an operation, it ensures the network matches. If it has changed the call will fail and throw a NETWORK_ERROR.

This extends to events. If an event filter is being listened for and the network changes, no further events will be emitted until the network is back where it belongs.

Provider special network "any"

There is now a special network name, "any", which if specified means the Provider will allow the backend network to change, meaning the network is not immutable.

If the network changes, a "network" event is emitted, and all calls pending on the network are deferred an additional event loop, which allows the callback to unregister events before any caller has access to the new network object. This also means that no events on the new network will be triggered before the "network" event. However, if your callback schedules something for the next event loop (e.g. await) it will occur after the network has possibly been read by another caller.

Some developers may not need to care about the current or changing networks, so this option is for them. But it is important that this type of operation is thought through.

The design pattern for many applications will likely be "on network change, shut down all events and cancel all inflight requests, and rebuild the hierarchy of events on the new network, set up the UI and provide feedback to the user".

For example, if you are monitoring an ERC-20 contract, the address may differ on the two networks, the UI will likely need the balances updated, available "approved" balances may need to be updated internally, a new banner should be included to indicate whether the network is a testnet and in that case that the funds are fake. New event filters will need to be setup (the old ones torn down) so that new incoming transfers are added to the UI transaction history as well. Make sure important caches your app manages are also discarded, such as ENS names and approved accounts.

I plan to provide code snippets at some point to illustrate this design pattern. But this is something that will be further down the road.

Simplest Solution

Most users never change their network, mostly this is something developers do during testing. So to keep like simple, you can simply force a page refresh when the network changes. Only developers should really notice this and it makes sure your UI and any network banners, etc. get a fresh start at setting up their world.

The do this, you can use something like:

// The "any" network will allow spontaneous network changes
const provider = new ethers.providers.Web3Provider(window.ethereum, "any");
provider.on("network", (newNetwork, oldNetwork) => {
    // When a Provider makes its initial connection, it emits a "network"
    // event with a null oldNetwork along with the newNetwork. So, if the
    // oldNetwork exists, it represents a changing network
    if (oldNetwork) {
        window.location.reload();
    }
});

Block Skew

As a somewhat related note, historically, if the "block" event was being listened for, the callback would be triggered for every block between the last poll cycle and the current poll cycle. So, if block 1000 was the last block from the previous poll, but the current block number is 1003, a "block" event would be emitted for 1001, 1002 and 1003.

Now if more than 1000 blocks passed between two poll cycles, then a block skew error will be emitted and the provider will reset from the current block. Less than 1000 blocks will still behave normally.

This historically only happened when a laptop was closed for a long duration or when changing networks (rinkeby vs ropsten have a large gap in block height), which could cause web pages to become unresponsive as the Provider tried to emit all the "block" events.

Now changing networks will only possible have a similar effect as the closed-laptop scenario, since the Provider is basically stalled; if the network change occurs for more than 1000 blocks, there will be block skew detected and will skip all the intermediate block events. This may happen if the application is in another tab the user ignores for quite some time.


Note: This does not affect any statically networked backends, like INFURA, Etherscan, etc. since those network backends cannot change; they are defined when the Provider is created. This is mainly for JsonRpcProviders, where a user shuts down their Geth node and brings it up with a new network, or the most common situation, a Web3Provider connected to MetaMask.

@rymnc
Copy link

rymnc commented Jul 2, 2020

thank you for providing this ! web3 does it automatically, thats why i was having issues porting to ethers :)

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Labels
META An Issue about Issues
Projects
None yet
Development

No branches or pull requests

2 participants