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 error in NetworkController:rpcEndpointUnavailable event #5501

Merged
merged 1 commit into from
Mar 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions packages/network-controller/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add support for automatic failover when Infura is unavailable ([#5630](https://github.com/MetaMask/core/pull/5630))
- An Infura RPC endpoint can now be configured with a list of failover URLs via `failoverUrls`.
- If, after many attempts, an Infura network is perceived to be down, the list of failover URLs will be tried in turn.
- Add messenger action `NetworkController:rpcEndpointUnavailable` for responding to when a RPC endpoint becomes unavailable (see above) ([#5492](https://github.com/MetaMask/core/pull/5492)
- Add messenger action `NetworkController:rpcEndpointUnavailable` for responding to when a RPC endpoint becomes unavailable (see above) ([#5492](https://github.com/MetaMask/core/pull/5492), [#5501](https://github.com/MetaMask/core/pull/5501))
- Also add associated type `NetworkControllerRpcEndpointUnavailableEvent`.
- Add messenger action `NetworkController:rpcEndpointDegraded` for responding to when a RPC endpoint becomes degraded ([#5492](https://github.com/MetaMask/core/pull/5492)
- Add messenger action `NetworkController:rpcEndpointDegraded` for responding to when a RPC endpoint becomes degraded ([#5492](https://github.com/MetaMask/core/pull/5492))
- Also add associated type `NetworkControllerRpcEndpointDegradedEvent`.
- Add messenger action `NetworkController:rpcEndpointRequestRetried` for responding to when a RPC endpoint is retried following a retriable error ([#5492](https://github.com/MetaMask/core/pull/5492)
- Add messenger action `NetworkController:rpcEndpointRequestRetried` for responding to when a RPC endpoint is retried following a retriable error ([#5492](https://github.com/MetaMask/core/pull/5492))
- Also add associated type `NetworkControllerRpcEndpointRequestRetriedEvent`.
- This is mainly useful for tests when mocking timers.
- Export `RpcServiceRequestable` type, which was previously named `AbstractRpcService` [#5492](https://github.com/MetaMask/core/pull/5492)
- Export `RpcServiceRequestable` type, which was previously named `AbstractRpcService` ([#5492](https://github.com/MetaMask/core/pull/5492))
- Export `isConnectionError` utility function ([#5501](https://github.com/MetaMask/core/pull/5501))
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 is the new entry. The other changes are just formatting fixes.


### Changed

Expand All @@ -37,9 +38,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- At minimum you will need to pass `fetch` and `btoa`.
- The `NetworkControllerOptions` also reflects this change.
- **BREAKING:** Add required property `failoverUrls` to `RpcEndpoint` ([#5630](https://github.com/MetaMask/core/pull/5630))
- The `NetworkControllerState` and the `state` option to `NetworkController` also reflects this change
- The `NetworkControllerState` and the `state` option to `NetworkController` also reflect this change
- **BREAKING:** Add required property `failoverRpcUrls` to `NetworkClientConfiguration` ([#5630](https://github.com/MetaMask/core/pull/5630))
- The `configuration` property in the `AutoManagedNetworkClient` and `NetworkClient` types also reflects this change
- The `configuration` property in the `AutoManagedNetworkClient` and `NetworkClient` types also reflect this change
- **BREAKING:** The `AbstractRpcService` type now has a non-optional `endpointUrl` property ([#5492](https://github.com/MetaMask/core/pull/5492))
- The old version of `AbstractRpcService` is now called `RpcServiceRequestable`
- Synchronize retry logic and error handling behavior between Infura and custom RPC endpoints ([#5290](https://github.com/MetaMask/core/pull/5290))
Expand Down
1 change: 1 addition & 0 deletions packages/network-controller/src/NetworkController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,7 @@ export type NetworkControllerRpcEndpointUnavailableEvent = {
chainId: Hex;
endpointUrl: string;
failoverEndpointUrl?: string;
error: unknown;
},
];
};
Expand Down
10 changes: 9 additions & 1 deletion packages/network-controller/src/create-network-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,19 @@ export function createNetworkClient({
endpointUrl,
})),
);
rpcService.onBreak(({ endpointUrl, failoverEndpointUrl }) => {
rpcService.onBreak(({ endpointUrl, failoverEndpointUrl, ...rest }) => {
let error: unknown;
if ('error' in rest) {
error = rest.error;
} else if ('value' in rest) {
error = rest.value;
}

messenger.publish('NetworkController:rpcEndpointUnavailable', {
chainId: configuration.chainId,
endpointUrl,
failoverEndpointUrl,
error,
});
});
rpcService.onDegraded(({ endpointUrl }) => {
Expand Down
1 change: 1 addition & 0 deletions packages/network-controller/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,4 @@ export { NetworkClientType } from './types';
export type { NetworkClient } from './create-network-client';
export type { AbstractRpcService } from './rpc-service/abstract-rpc-service';
export type { RpcServiceRequestable } from './rpc-service/rpc-service-requestable';
export { isConnectionError } from './rpc-service/rpc-service';
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ export const CONNECTION_ERRORS = [
* @returns True if the error indicates that the network cannot be connected to,
* and false otherwise.
*/
export default function isConnectionError(error: unknown) {
export function isConnectionError(error: unknown) {
if (!(typeof error === 'object' && error !== null && 'message' in error)) {
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -284,8 +284,8 @@ export function testsForRpcMethodsThatCheckForBlockHashInResponse(
}

describe.each([
[405, 'The method does not exist / is not available'],
[429, 'Request is being rate limited'],
[405, 'The method does not exist / is not available.'],
[429, 'Request is being rate limited.'],
])(
'if the RPC endpoint returns a %d response',
(httpStatus, errorMessage) => {
Expand Down Expand Up @@ -424,6 +424,9 @@ export function testsForRpcMethodsThatCheckForBlockHashInResponse(
chainId,
endpointUrl: rpcUrl,
failoverEndpointUrl: 'https://failover.endpoint/',
error: expect.objectContaining({
message: errorMessage,
}),
});
},
);
Expand Down Expand Up @@ -493,6 +496,9 @@ export function testsForRpcMethodsThatCheckForBlockHashInResponse(
).toHaveBeenNthCalledWith(2, {
chainId,
endpointUrl: 'https://failover.endpoint/',
error: expect.objectContaining({
message: errorMessage,
}),
});
},
);
Expand Down Expand Up @@ -611,6 +617,7 @@ export function testsForRpcMethodsThatCheckForBlockHashInResponse(

describe('if the RPC endpoint returns a response that is not 405, 429, 503, or 504', () => {
const httpStatus = 500;
const errorMessage = `Non-200 status code: '${httpStatus}'`;

it('throws a generic, undescriptive error', async () => {
await withMockedCommunications({ providerType }, async (comms) => {
Expand Down Expand Up @@ -746,6 +753,9 @@ export function testsForRpcMethodsThatCheckForBlockHashInResponse(
chainId,
endpointUrl: rpcUrl,
failoverEndpointUrl: 'https://failover.endpoint/',
error: expect.objectContaining({
message: errorMessage,
}),
},
);
},
Expand Down Expand Up @@ -813,6 +823,9 @@ export function testsForRpcMethodsThatCheckForBlockHashInResponse(
).toHaveBeenNthCalledWith(2, {
chainId,
endpointUrl: 'https://failover.endpoint/',
error: expect.objectContaining({
message: errorMessage,
}),
});
},
);
Expand Down Expand Up @@ -925,6 +938,8 @@ export function testsForRpcMethodsThatCheckForBlockHashInResponse(
describe.each([503, 504])(
'if the RPC endpoint returns a %d response',
(httpStatus) => {
const errorMessage = 'Gateway timeout';

it('retries the request up to 5 times until there is a 200 response', async () => {
await withMockedCommunications({ providerType }, async (comms) => {
const request = { method };
Expand Down Expand Up @@ -990,7 +1005,7 @@ export function testsForRpcMethodsThatCheckForBlockHashInResponse(
);
},
);
await expect(promiseForResult).rejects.toThrow('Gateway timeout');
await expect(promiseForResult).rejects.toThrow(errorMessage);
});
});

Expand Down Expand Up @@ -1131,6 +1146,9 @@ export function testsForRpcMethodsThatCheckForBlockHashInResponse(
chainId,
endpointUrl: rpcUrl,
failoverEndpointUrl: 'https://failover.endpoint/',
error: expect.objectContaining({
message: expect.stringContaining(errorMessage),
}),
});
},
);
Expand Down Expand Up @@ -1231,6 +1249,9 @@ export function testsForRpcMethodsThatCheckForBlockHashInResponse(
).toHaveBeenNthCalledWith(2, {
chainId,
endpointUrl: 'https://failover.endpoint/',
error: expect.objectContaining({
message: expect.stringContaining(errorMessage),
}),
});
},
);
Expand Down Expand Up @@ -1560,6 +1581,9 @@ export function testsForRpcMethodsThatCheckForBlockHashInResponse(
chainId,
endpointUrl: rpcUrl,
failoverEndpointUrl: 'https://failover.endpoint/',
error: expect.objectContaining({
message: `request to ${rpcUrl} failed, reason: ${errorCode}`,
}),
});
},
);
Expand Down Expand Up @@ -1658,6 +1682,9 @@ export function testsForRpcMethodsThatCheckForBlockHashInResponse(
).toHaveBeenNthCalledWith(2, {
chainId,
endpointUrl: 'https://failover.endpoint/',
error: expect.objectContaining({
message: `request to https://failover.endpoint/ failed, reason: ${errorCode}`,
}),
});
},
);
Expand Down Expand Up @@ -1777,6 +1804,8 @@ export function testsForRpcMethodsThatCheckForBlockHashInResponse(
);

describe('if the RPC endpoint responds with invalid JSON', () => {
const errorMessage = 'not valid JSON';

it('retries the request up to 5 times until it responds with valid JSON', async () => {
await withMockedCommunications({ providerType }, async (comms) => {
const request = { method };
Expand Down Expand Up @@ -1841,7 +1870,7 @@ export function testsForRpcMethodsThatCheckForBlockHashInResponse(
},
);

await expect(promiseForResult).rejects.toThrow('not valid JSON');
await expect(promiseForResult).rejects.toThrow(errorMessage);
});
});

Expand Down Expand Up @@ -1973,6 +2002,9 @@ export function testsForRpcMethodsThatCheckForBlockHashInResponse(
chainId,
endpointUrl: rpcUrl,
failoverEndpointUrl: 'https://failover.endpoint/',
error: expect.objectContaining({
message: expect.stringContaining(errorMessage),
}),
},
);
},
Expand Down Expand Up @@ -2068,6 +2100,9 @@ export function testsForRpcMethodsThatCheckForBlockHashInResponse(
).toHaveBeenNthCalledWith(2, {
chainId,
endpointUrl: 'https://failover.endpoint/',
error: expect.objectContaining({
message: expect.stringContaining(errorMessage),
}),
});
},
);
Expand Down Expand Up @@ -2371,6 +2406,9 @@ export function testsForRpcMethodsThatCheckForBlockHashInResponse(
chainId,
endpointUrl: rpcUrl,
failoverEndpointUrl: 'https://failover.endpoint/',
error: expect.objectContaining({
message: `request to ${rpcUrl} failed, reason: Failed to fetch`,
}),
},
);
},
Expand Down Expand Up @@ -2464,6 +2502,9 @@ export function testsForRpcMethodsThatCheckForBlockHashInResponse(
).toHaveBeenNthCalledWith(2, {
chainId,
endpointUrl: 'https://failover.endpoint/',
error: expect.objectContaining({
message: `request to https://failover.endpoint/ failed, reason: Failed to fetch`,
}),
});
},
);
Expand Down
Loading
Loading