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

Integrate RPC failover into NetworkController #5360

Open
wants to merge 10 commits into
base: main
Choose a base branch
from

Conversation

mcmire
Copy link
Contributor

@mcmire mcmire commented Feb 18, 2025

Explanation

When configuring an Infura network, a list of failover endpoint URLs can now be supplied. If Infura is perceived to be down (after 15 unsuccessful attempts to make a request) then the request will be forwarded automatically to the first failover URL (using subsequent URLs as failovers for previous URLs). By default, all Infura networks will use Quicknode as a failover.

Under the hood, the list of failover URLs are loaded into an RpcServiceChain (added in a previous commit) and this is then passed to the Infura middleware.

References

Closes #4993.

Changelog

(Updated in PR.)

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 (working on it!)

@mcmire mcmire force-pushed the integrate-rpc-service-chain-into-nc branch from 5df00c8 to 0c25600 Compare February 18, 2025 22:23
When configuring an Infura network, a list of failover endpoint URLs can
now be supplied. If Infura is perceived to be down (after making 15
attempts to make a request, all of which either throw an error, return a
non-200 response, or return a non-parseable response) then the request
will be forwarded automatically to the first failover URL (using
subsequent URLs as failovers for previous URLs). By default, all Infura
networks will use Quicknode as a failover.

Under the hood, the list of failover URLs are loaded into an
RpcServiceChain (added in a previous commit) and this is then passed to
the Infura middleware.
@mcmire mcmire force-pushed the integrate-rpc-service-chain-into-nc branch from 0c25600 to e2b3b18 Compare February 18, 2025 22:29
@mcmire
Copy link
Contributor Author

mcmire commented Feb 18, 2025

@metamaskbot publish-previews

Copy link
Contributor

Preview builds have been published. See these instructions for more information about preview builds.

Expand for full list of packages and versions.
{
  "@metamask-previews/accounts-controller": "24.0.0-preview-e2b3b183",
  "@metamask-previews/address-book-controller": "6.0.3-preview-e2b3b183",
  "@metamask-previews/announcement-controller": "7.0.3-preview-e2b3b183",
  "@metamask-previews/approval-controller": "7.1.3-preview-e2b3b183",
  "@metamask-previews/assets-controllers": "50.0.0-preview-e2b3b183",
  "@metamask-previews/base-controller": "8.0.0-preview-e2b3b183",
  "@metamask-previews/bridge-controller": "0.0.0-preview-e2b3b183",
  "@metamask-previews/build-utils": "3.0.3-preview-e2b3b183",
  "@metamask-previews/composable-controller": "11.0.0-preview-e2b3b183",
  "@metamask-previews/controller-utils": "11.5.0-preview-e2b3b183",
  "@metamask-previews/earn-controller": "0.4.0-preview-e2b3b183",
  "@metamask-previews/ens-controller": "15.0.2-preview-e2b3b183",
  "@metamask-previews/eth-json-rpc-provider": "4.1.8-preview-e2b3b183",
  "@metamask-previews/gas-fee-controller": "22.0.3-preview-e2b3b183",
  "@metamask-previews/json-rpc-engine": "10.0.3-preview-e2b3b183",
  "@metamask-previews/json-rpc-middleware-stream": "8.0.7-preview-e2b3b183",
  "@metamask-previews/keyring-controller": "19.2.0-preview-e2b3b183",
  "@metamask-previews/logging-controller": "6.0.4-preview-e2b3b183",
  "@metamask-previews/message-manager": "12.0.1-preview-e2b3b183",
  "@metamask-previews/multichain": "2.1.1-preview-e2b3b183",
  "@metamask-previews/multichain-network-controller": "0.1.1-preview-e2b3b183",
  "@metamask-previews/multichain-transactions-controller": "0.4.0-preview-e2b3b183",
  "@metamask-previews/name-controller": "8.0.3-preview-e2b3b183",
  "@metamask-previews/network-controller": "22.2.1-preview-e2b3b183",
  "@metamask-previews/notification-services-controller": "0.21.0-preview-e2b3b183",
  "@metamask-previews/permission-controller": "11.0.6-preview-e2b3b183",
  "@metamask-previews/permission-log-controller": "3.0.3-preview-e2b3b183",
  "@metamask-previews/phishing-controller": "12.3.2-preview-e2b3b183",
  "@metamask-previews/polling-controller": "12.0.3-preview-e2b3b183",
  "@metamask-previews/preferences-controller": "15.0.2-preview-e2b3b183",
  "@metamask-previews/profile-sync-controller": "8.1.0-preview-e2b3b183",
  "@metamask-previews/queued-request-controller": "9.0.1-preview-e2b3b183",
  "@metamask-previews/rate-limit-controller": "6.0.3-preview-e2b3b183",
  "@metamask-previews/remote-feature-flag-controller": "1.5.0-preview-e2b3b183",
  "@metamask-previews/selected-network-controller": "21.0.1-preview-e2b3b183",
  "@metamask-previews/signature-controller": "23.2.1-preview-e2b3b183",
  "@metamask-previews/token-search-discovery-controller": "2.1.0-preview-e2b3b183",
  "@metamask-previews/transaction-controller": "46.0.0-preview-e2b3b183",
  "@metamask-previews/user-operation-controller": "25.0.0-preview-e2b3b183"
}

: configuration.rpcUrl;
const failoverEndpointUrls =
configuration.type === NetworkClientType.Infura
? configuration.failoverEndpointUrls
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 property is new on the network client configuration. Now that we have it, we can feed it into an RpcServiceChain. This class will automatically handle the failover.

@@ -345,119 +346,125 @@ export function testsForRpcMethodSupportingBlockParam(
});
});

it('throws an error with a custom message if the request to the RPC endpoint returns a 405 response', async () => {
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 diff here is pretty ugly, but I added a new test for each of the existing failure cases which exercise the failover behavior.

async (primaryComms) => {
await withMockedCommunications(
{
providerType: 'custom',
Copy link
Contributor Author

@mcmire mcmire Feb 19, 2025

Choose a reason for hiding this comment

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

It's kind of weird that we have to nest withMockedCommunications, and even weirder that we are supposedly treating the failover endpoint as a custom RPC endpoint. But this is just because the API for withMockedCommunications is awkward, and it would be out of scope to fix it, so I used what I had.

At the end of the day, though, what we are doing is mocking requests to two different endpoints, which should make sense as we now have the failover endpoint that we are hitting.

@@ -2617,6 +2627,7 @@ export class NetworkController extends BaseController<
networkClientConfiguration: {
type: NetworkClientType.Infura,
network: infuraNetworkName,
failoverEndpointUrls: rpcEndpoint.failoverUrls,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

There is a new property on InfuraRpcEndpoint and this is where that property is used to create the network client.

@mcmire mcmire marked this pull request as ready for review February 19, 2025 18:32
@mcmire mcmire requested review from a team as code owners February 19, 2025 18:32
@@ -387,6 +387,7 @@ describe('NetworkController', () => {
"nativeCurrency": "ETH",
"rpcEndpoints": Array [
Object {
"failoverUrls": Array [],
Copy link
Contributor Author

Choose a reason for hiding this comment

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

There are a few kinds of changes being made to these tests:

  • For tests which do not create a new NetworkController with a specific set of network configurations, we need to now verify that Infura RPC endpoints use an empty list of failover URLs.
  • For tests which do create a new NetworkController with a specific set of network configurations, particularly if we are mocking createNetworkClient and testing the presence of network configurations in state, we pass in an explicit set of failover RPC endpoints so we can use it for comparison. I guess in most cases we don't need to care about this, it's a lot of extra information, but that's how the tests are written.

@@ -274,4 +279,707 @@ export function testsForRpcMethodsThatCheckForBlockHashInResponse(
});
});
}

describe.each([
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 file (which is used to test two RPC methods) didn't have any tests around handling of responses. I originally wrote them for block-param.ts and no-block-param.ts and then copied them over.

@@ -235,86 +236,456 @@ export function testsForRpcMethodAssumingNoBlockParam(
});
});

it('throws a custom error if the request to the RPC endpoint returns a 405 response', async () => {
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 changes to this file are a mirror of block-param.ts.

@mcmire
Copy link
Contributor Author

mcmire commented Feb 19, 2025

@metamaskbot publish-previews

Copy link
Contributor

Preview builds have been published. See these instructions for more information about preview builds.

Expand for full list of packages and versions.
{
  "@metamask-previews/accounts-controller": "24.0.0-preview-a1eb992b",
  "@metamask-previews/address-book-controller": "6.0.3-preview-a1eb992b",
  "@metamask-previews/announcement-controller": "7.0.3-preview-a1eb992b",
  "@metamask-previews/approval-controller": "7.1.3-preview-a1eb992b",
  "@metamask-previews/assets-controllers": "50.0.0-preview-a1eb992b",
  "@metamask-previews/base-controller": "8.0.0-preview-a1eb992b",
  "@metamask-previews/bridge-controller": "0.0.0-preview-a1eb992b",
  "@metamask-previews/build-utils": "3.0.3-preview-a1eb992b",
  "@metamask-previews/composable-controller": "11.0.0-preview-a1eb992b",
  "@metamask-previews/controller-utils": "11.5.0-preview-a1eb992b",
  "@metamask-previews/earn-controller": "0.4.0-preview-a1eb992b",
  "@metamask-previews/ens-controller": "15.0.2-preview-a1eb992b",
  "@metamask-previews/eth-json-rpc-provider": "4.1.8-preview-a1eb992b",
  "@metamask-previews/gas-fee-controller": "22.0.3-preview-a1eb992b",
  "@metamask-previews/json-rpc-engine": "10.0.3-preview-a1eb992b",
  "@metamask-previews/json-rpc-middleware-stream": "8.0.7-preview-a1eb992b",
  "@metamask-previews/keyring-controller": "19.2.0-preview-a1eb992b",
  "@metamask-previews/logging-controller": "6.0.4-preview-a1eb992b",
  "@metamask-previews/message-manager": "12.0.1-preview-a1eb992b",
  "@metamask-previews/multichain": "2.1.1-preview-a1eb992b",
  "@metamask-previews/multichain-network-controller": "0.1.1-preview-a1eb992b",
  "@metamask-previews/multichain-transactions-controller": "0.4.0-preview-a1eb992b",
  "@metamask-previews/name-controller": "8.0.3-preview-a1eb992b",
  "@metamask-previews/network-controller": "22.2.1-preview-a1eb992b",
  "@metamask-previews/notification-services-controller": "0.21.0-preview-a1eb992b",
  "@metamask-previews/permission-controller": "11.0.6-preview-a1eb992b",
  "@metamask-previews/permission-log-controller": "3.0.3-preview-a1eb992b",
  "@metamask-previews/phishing-controller": "12.3.2-preview-a1eb992b",
  "@metamask-previews/polling-controller": "12.0.3-preview-a1eb992b",
  "@metamask-previews/preferences-controller": "15.0.2-preview-a1eb992b",
  "@metamask-previews/profile-sync-controller": "8.1.0-preview-a1eb992b",
  "@metamask-previews/queued-request-controller": "9.0.1-preview-a1eb992b",
  "@metamask-previews/rate-limit-controller": "6.0.3-preview-a1eb992b",
  "@metamask-previews/remote-feature-flag-controller": "1.5.0-preview-a1eb992b",
  "@metamask-previews/selected-network-controller": "21.0.1-preview-a1eb992b",
  "@metamask-previews/signature-controller": "23.2.1-preview-a1eb992b",
  "@metamask-previews/token-search-discovery-controller": "2.1.0-preview-a1eb992b",
  "@metamask-previews/transaction-controller": "46.0.0-preview-a1eb992b",
  "@metamask-previews/user-operation-controller": "25.0.0-preview-a1eb992b"
}

mirceanis
mirceanis previously approved these changes Feb 20, 2025
Copy link

@mirceanis mirceanis left a comment

Choose a reason for hiding this comment

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

identity code-owner approval

@mcmire
Copy link
Contributor Author

mcmire commented Feb 21, 2025

I'll need to update this PR to also support custom RPC endpoints. I'll request another approval when ready.

@@ -430,9 +425,8 @@ export async function waitForPromiseToBeFulfilledAfterRunningAllTimers(

// `hasPromiseBeenFulfilled` is modified asynchronously.
/* eslint-disable-next-line no-unmodified-loop-condition */
while (!hasPromiseBeenFulfilled && numTimesClockHasBeenAdvanced < 15) {
clock.runAll();
await new Promise((resolve) => originalSetTimeout(resolve, 10));
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Making this change was necessary to get the new provider API tests I'm adding for the failover logic to work without running into timeout issues.

btoa,
})
.mockReturnValue(fakeNetworkClients[1]);
createNetworkClientMock.mockImplementation(
Copy link
Contributor Author

@mcmire mcmire Feb 24, 2025

Choose a reason for hiding this comment

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

There were tons of tests that were doing an exact match when mocking the call to createNetworkClient (if the network client configuration is exactly this, return this fake network client; if it's exactly this, return another one; etc.). We don't need to do this; in most cases we can figure out which network client to return by doing a simpler check. I took the opportunity to clean this up as I was updating these tests. This also allows us to not worry about whether the RPC endpoints that we use to set up the test are configured with failover URLs or not.

@mcmire mcmire requested a review from a team as a code owner February 24, 2025 21:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[network-controller] Add failover support
2 participants