From deb4732d2a9a40da2df85793659c168ee1ab7e35 Mon Sep 17 00:00:00 2001
From: Elliot Winkler <elliot.winkler@gmail.com>
Date: Tue, 18 Mar 2025 17:43:44 -0600
Subject: [PATCH] Expose error in NetworkController:rpcEndpointUnavailable
 event

In the client we want the ability to decide whether or not to create a
Segment error when a request to Infura fails too many times in a row.
Specifically, we want to be able to ignore connection errors.

To accommodate this:

- Expose the error that was encountered while making the request in the
  `NetworkController:rpcEndpointUnavailable` event data.
- Export `isConnectionError` from `RpcService`.
---
 packages/network-controller/CHANGELOG.md      | 13 ++---
 .../src/NetworkController.ts                  |  1 +
 .../src/create-network-client.ts              | 10 +++-
 packages/network-controller/src/index.ts      |  1 +
 .../src/rpc-service/rpc-service.ts            |  2 +-
 .../block-hash-in-response.ts                 | 49 +++++++++++++++--
 .../tests/provider-api-tests/block-param.ts   | 53 ++++++++++++++++---
 .../provider-api-tests/no-block-param.ts      | 53 ++++++++++++++++---
 8 files changed, 156 insertions(+), 26 deletions(-)

diff --git a/packages/network-controller/CHANGELOG.md b/packages/network-controller/CHANGELOG.md
index 89b6eed1284..d484c207a21 100644
--- a/packages/network-controller/CHANGELOG.md
+++ b/packages/network-controller/CHANGELOG.md
@@ -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))
 
 ### Changed
 
@@ -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))
diff --git a/packages/network-controller/src/NetworkController.ts b/packages/network-controller/src/NetworkController.ts
index 9ae7fcf87ed..728fe268b1c 100644
--- a/packages/network-controller/src/NetworkController.ts
+++ b/packages/network-controller/src/NetworkController.ts
@@ -444,6 +444,7 @@ export type NetworkControllerRpcEndpointUnavailableEvent = {
       chainId: Hex;
       endpointUrl: string;
       failoverEndpointUrl?: string;
+      error: unknown;
     },
   ];
 };
diff --git a/packages/network-controller/src/create-network-client.ts b/packages/network-controller/src/create-network-client.ts
index d0f6c182133..41d9be6700f 100644
--- a/packages/network-controller/src/create-network-client.ts
+++ b/packages/network-controller/src/create-network-client.ts
@@ -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 }) => {
diff --git a/packages/network-controller/src/index.ts b/packages/network-controller/src/index.ts
index 04dee63930e..42f264deb1e 100644
--- a/packages/network-controller/src/index.ts
+++ b/packages/network-controller/src/index.ts
@@ -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';
diff --git a/packages/network-controller/src/rpc-service/rpc-service.ts b/packages/network-controller/src/rpc-service/rpc-service.ts
index ed6b791434f..e2766fd2a08 100644
--- a/packages/network-controller/src/rpc-service/rpc-service.ts
+++ b/packages/network-controller/src/rpc-service/rpc-service.ts
@@ -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;
   }
diff --git a/packages/network-controller/tests/provider-api-tests/block-hash-in-response.ts b/packages/network-controller/tests/provider-api-tests/block-hash-in-response.ts
index 45190c862fd..80f411757af 100644
--- a/packages/network-controller/tests/provider-api-tests/block-hash-in-response.ts
+++ b/packages/network-controller/tests/provider-api-tests/block-hash-in-response.ts
@@ -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) => {
@@ -424,6 +424,9 @@ export function testsForRpcMethodsThatCheckForBlockHashInResponse(
                       chainId,
                       endpointUrl: rpcUrl,
                       failoverEndpointUrl: 'https://failover.endpoint/',
+                      error: expect.objectContaining({
+                        message: errorMessage,
+                      }),
                     });
                   },
                 );
@@ -493,6 +496,9 @@ export function testsForRpcMethodsThatCheckForBlockHashInResponse(
                     ).toHaveBeenNthCalledWith(2, {
                       chainId,
                       endpointUrl: 'https://failover.endpoint/',
+                      error: expect.objectContaining({
+                        message: errorMessage,
+                      }),
                     });
                   },
                 );
@@ -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) => {
@@ -746,6 +753,9 @@ export function testsForRpcMethodsThatCheckForBlockHashInResponse(
                     chainId,
                     endpointUrl: rpcUrl,
                     failoverEndpointUrl: 'https://failover.endpoint/',
+                    error: expect.objectContaining({
+                      message: errorMessage,
+                    }),
                   },
                 );
               },
@@ -813,6 +823,9 @@ export function testsForRpcMethodsThatCheckForBlockHashInResponse(
                 ).toHaveBeenNthCalledWith(2, {
                   chainId,
                   endpointUrl: 'https://failover.endpoint/',
+                  error: expect.objectContaining({
+                    message: errorMessage,
+                  }),
                 });
               },
             );
@@ -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 };
@@ -990,7 +1005,7 @@ export function testsForRpcMethodsThatCheckForBlockHashInResponse(
               );
             },
           );
-          await expect(promiseForResult).rejects.toThrow('Gateway timeout');
+          await expect(promiseForResult).rejects.toThrow(errorMessage);
         });
       });
 
@@ -1131,6 +1146,9 @@ export function testsForRpcMethodsThatCheckForBlockHashInResponse(
                       chainId,
                       endpointUrl: rpcUrl,
                       failoverEndpointUrl: 'https://failover.endpoint/',
+                      error: expect.objectContaining({
+                        message: expect.stringContaining(errorMessage),
+                      }),
                     });
                   },
                 );
@@ -1231,6 +1249,9 @@ export function testsForRpcMethodsThatCheckForBlockHashInResponse(
                     ).toHaveBeenNthCalledWith(2, {
                       chainId,
                       endpointUrl: 'https://failover.endpoint/',
+                      error: expect.objectContaining({
+                        message: expect.stringContaining(errorMessage),
+                      }),
                     });
                   },
                 );
@@ -1560,6 +1581,9 @@ export function testsForRpcMethodsThatCheckForBlockHashInResponse(
                       chainId,
                       endpointUrl: rpcUrl,
                       failoverEndpointUrl: 'https://failover.endpoint/',
+                      error: expect.objectContaining({
+                        message: `request to ${rpcUrl} failed, reason: ${errorCode}`,
+                      }),
                     });
                   },
                 );
@@ -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}`,
+                      }),
                     });
                   },
                 );
@@ -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 };
@@ -1841,7 +1870,7 @@ export function testsForRpcMethodsThatCheckForBlockHashInResponse(
           },
         );
 
-        await expect(promiseForResult).rejects.toThrow('not valid JSON');
+        await expect(promiseForResult).rejects.toThrow(errorMessage);
       });
     });
 
@@ -1973,6 +2002,9 @@ export function testsForRpcMethodsThatCheckForBlockHashInResponse(
                     chainId,
                     endpointUrl: rpcUrl,
                     failoverEndpointUrl: 'https://failover.endpoint/',
+                    error: expect.objectContaining({
+                      message: expect.stringContaining(errorMessage),
+                    }),
                   },
                 );
               },
@@ -2068,6 +2100,9 @@ export function testsForRpcMethodsThatCheckForBlockHashInResponse(
                 ).toHaveBeenNthCalledWith(2, {
                   chainId,
                   endpointUrl: 'https://failover.endpoint/',
+                  error: expect.objectContaining({
+                    message: expect.stringContaining(errorMessage),
+                  }),
                 });
               },
             );
@@ -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`,
+                    }),
                   },
                 );
               },
@@ -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`,
+                  }),
                 });
               },
             );
diff --git a/packages/network-controller/tests/provider-api-tests/block-param.ts b/packages/network-controller/tests/provider-api-tests/block-param.ts
index 2c02aff92d1..b5f2e49b501 100644
--- a/packages/network-controller/tests/provider-api-tests/block-param.ts
+++ b/packages/network-controller/tests/provider-api-tests/block-param.ts
@@ -350,8 +350,8 @@ export function testsForRpcMethodSupportingBlockParam(
     });
 
     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) => {
@@ -531,6 +531,9 @@ export function testsForRpcMethodSupportingBlockParam(
                         chainId,
                         endpointUrl: rpcUrl,
                         failoverEndpointUrl: 'https://failover.endpoint/',
+                        error: expect.objectContaining({
+                          message: errorMessage,
+                        }),
                       });
                     },
                   );
@@ -613,6 +616,9 @@ export function testsForRpcMethodSupportingBlockParam(
                       ).toHaveBeenNthCalledWith(2, {
                         chainId,
                         endpointUrl: 'https://failover.endpoint/',
+                        error: expect.objectContaining({
+                          message: errorMessage,
+                        }),
                       });
                     },
                   );
@@ -747,6 +753,7 @@ export function testsForRpcMethodSupportingBlockParam(
 
     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) => {
@@ -783,9 +790,7 @@ export function testsForRpcMethodSupportingBlockParam(
             async ({ makeRpcCall }) => makeRpcCall(request),
           );
 
-          await expect(promiseForResult).rejects.toThrow(
-            `Non-200 status code: '${httpStatus}'`,
-          );
+          await expect(promiseForResult).rejects.toThrow(errorMessage);
         });
       });
 
@@ -929,6 +934,9 @@ export function testsForRpcMethodSupportingBlockParam(
                       chainId,
                       endpointUrl: rpcUrl,
                       failoverEndpointUrl: 'https://failover.endpoint/',
+                      error: expect.objectContaining({
+                        message: errorMessage,
+                      }),
                     });
                   },
                 );
@@ -1011,6 +1019,9 @@ export function testsForRpcMethodSupportingBlockParam(
                     ).toHaveBeenNthCalledWith(2, {
                       chainId,
                       endpointUrl: 'https://failover.endpoint/',
+                      error: expect.objectContaining({
+                        message: errorMessage,
+                      }),
                     });
                   },
                 );
@@ -1145,6 +1156,8 @@ export function testsForRpcMethodSupportingBlockParam(
     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 = {
@@ -1240,7 +1253,7 @@ export function testsForRpcMethodSupportingBlockParam(
                 );
               },
             );
-            await expect(promiseForResult).rejects.toThrow('Gateway timeout');
+            await expect(promiseForResult).rejects.toThrow(errorMessage);
           });
         });
 
@@ -1409,6 +1422,9 @@ export function testsForRpcMethodSupportingBlockParam(
                         chainId,
                         endpointUrl: rpcUrl,
                         failoverEndpointUrl: 'https://failover.endpoint/',
+                        error: expect.objectContaining({
+                          message: expect.stringContaining(errorMessage),
+                        }),
                       });
                     },
                   );
@@ -1524,6 +1540,9 @@ export function testsForRpcMethodSupportingBlockParam(
                       ).toHaveBeenNthCalledWith(2, {
                         chainId,
                         endpointUrl: 'https://failover.endpoint/',
+                        error: expect.objectContaining({
+                          message: expect.stringContaining(errorMessage),
+                        }),
                       });
                     },
                   );
@@ -1926,6 +1945,9 @@ export function testsForRpcMethodSupportingBlockParam(
                         chainId,
                         endpointUrl: rpcUrl,
                         failoverEndpointUrl: 'https://failover.endpoint/',
+                        error: expect.objectContaining({
+                          message: `request to ${rpcUrl} failed, reason: ${errorCode}`,
+                        }),
                       });
                     },
                   );
@@ -2039,6 +2061,9 @@ export function testsForRpcMethodSupportingBlockParam(
                       ).toHaveBeenNthCalledWith(2, {
                         chainId,
                         endpointUrl: 'https://failover.endpoint/',
+                        error: expect.objectContaining({
+                          message: `request to https://failover.endpoint/ failed, reason: ${errorCode}`,
+                        }),
                       });
                     },
                   );
@@ -2171,6 +2196,8 @@ export function testsForRpcMethodSupportingBlockParam(
     );
 
     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 = {
@@ -2266,7 +2293,7 @@ export function testsForRpcMethodSupportingBlockParam(
             },
           );
 
-          await expect(promiseForResult).rejects.toThrow('not valid JSON');
+          await expect(promiseForResult).rejects.toThrow(errorMessage);
         });
       });
 
@@ -2430,6 +2457,9 @@ export function testsForRpcMethodSupportingBlockParam(
                       chainId,
                       endpointUrl: rpcUrl,
                       failoverEndpointUrl: 'https://failover.endpoint/',
+                      error: expect.objectContaining({
+                        message: expect.stringContaining(errorMessage),
+                      }),
                     });
                   },
                 );
@@ -2543,6 +2573,9 @@ export function testsForRpcMethodSupportingBlockParam(
                     ).toHaveBeenNthCalledWith(2, {
                       chainId,
                       endpointUrl: 'https://failover.endpoint/',
+                      error: expect.objectContaining({
+                        message: expect.stringContaining(errorMessage),
+                      }),
                     });
                   },
                 );
@@ -2927,6 +2960,9 @@ export function testsForRpcMethodSupportingBlockParam(
                       chainId,
                       endpointUrl: rpcUrl,
                       failoverEndpointUrl: 'https://failover.endpoint/',
+                      error: expect.objectContaining({
+                        message: `request to ${rpcUrl} failed, reason: Failed to fetch`,
+                      }),
                     });
                   },
                 );
@@ -3037,6 +3073,9 @@ export function testsForRpcMethodSupportingBlockParam(
                     ).toHaveBeenNthCalledWith(2, {
                       chainId,
                       endpointUrl: 'https://failover.endpoint/',
+                      error: expect.objectContaining({
+                        message: `request to https://failover.endpoint/ failed, reason: Failed to fetch`,
+                      }),
                     });
                   },
                 );
diff --git a/packages/network-controller/tests/provider-api-tests/no-block-param.ts b/packages/network-controller/tests/provider-api-tests/no-block-param.ts
index a179148af56..f66ca1e3d94 100644
--- a/packages/network-controller/tests/provider-api-tests/no-block-param.ts
+++ b/packages/network-controller/tests/provider-api-tests/no-block-param.ts
@@ -240,8 +240,8 @@ export function testsForRpcMethodAssumingNoBlockParam(
   });
 
   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) => {
@@ -380,6 +380,9 @@ export function testsForRpcMethodAssumingNoBlockParam(
                       chainId,
                       endpointUrl: rpcUrl,
                       failoverEndpointUrl: 'https://failover.endpoint/',
+                      error: expect.objectContaining({
+                        message: errorMessage,
+                      }),
                     });
                   },
                 );
@@ -449,6 +452,9 @@ export function testsForRpcMethodAssumingNoBlockParam(
                     ).toHaveBeenNthCalledWith(2, {
                       chainId,
                       endpointUrl: 'https://failover.endpoint/',
+                      error: expect.objectContaining({
+                        message: errorMessage,
+                      }),
                     });
                   },
                 );
@@ -567,6 +573,7 @@ export function testsForRpcMethodAssumingNoBlockParam(
 
   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) => {
@@ -587,9 +594,7 @@ export function testsForRpcMethodAssumingNoBlockParam(
           async ({ makeRpcCall }) => makeRpcCall(request),
         );
 
-        await expect(promiseForResult).rejects.toThrow(
-          `Non-200 status code: '${httpStatus}'`,
-        );
+        await expect(promiseForResult).rejects.toThrow(errorMessage);
       });
     });
 
@@ -702,6 +707,9 @@ export function testsForRpcMethodAssumingNoBlockParam(
                     chainId,
                     endpointUrl: rpcUrl,
                     failoverEndpointUrl: 'https://failover.endpoint/',
+                    error: expect.objectContaining({
+                      message: errorMessage,
+                    }),
                   },
                 );
               },
@@ -769,6 +777,9 @@ export function testsForRpcMethodAssumingNoBlockParam(
                 ).toHaveBeenNthCalledWith(2, {
                   chainId,
                   endpointUrl: 'https://failover.endpoint/',
+                  error: expect.objectContaining({
+                    message: errorMessage,
+                  }),
                 });
               },
             );
@@ -881,6 +892,8 @@ export function testsForRpcMethodAssumingNoBlockParam(
   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 };
@@ -946,7 +959,7 @@ export function testsForRpcMethodAssumingNoBlockParam(
               );
             },
           );
-          await expect(promiseForResult).rejects.toThrow('Gateway timeout');
+          await expect(promiseForResult).rejects.toThrow(errorMessage);
         });
       });
 
@@ -1087,6 +1100,9 @@ export function testsForRpcMethodAssumingNoBlockParam(
                       chainId,
                       endpointUrl: rpcUrl,
                       failoverEndpointUrl: 'https://failover.endpoint/',
+                      error: expect.objectContaining({
+                        message: expect.stringContaining(errorMessage),
+                      }),
                     });
                   },
                 );
@@ -1187,6 +1203,9 @@ export function testsForRpcMethodAssumingNoBlockParam(
                     ).toHaveBeenNthCalledWith(2, {
                       chainId,
                       endpointUrl: 'https://failover.endpoint/',
+                      error: expect.objectContaining({
+                        message: expect.stringContaining(errorMessage),
+                      }),
                     });
                   },
                 );
@@ -1516,6 +1535,9 @@ export function testsForRpcMethodAssumingNoBlockParam(
                       chainId,
                       endpointUrl: rpcUrl,
                       failoverEndpointUrl: 'https://failover.endpoint/',
+                      error: expect.objectContaining({
+                        message: `request to ${rpcUrl} failed, reason: ${errorCode}`,
+                      }),
                     });
                   },
                 );
@@ -1614,6 +1636,9 @@ export function testsForRpcMethodAssumingNoBlockParam(
                     ).toHaveBeenNthCalledWith(2, {
                       chainId,
                       endpointUrl: 'https://failover.endpoint/',
+                      error: expect.objectContaining({
+                        message: `request to https://failover.endpoint/ failed, reason: ${errorCode}`,
+                      }),
                     });
                   },
                 );
@@ -1733,6 +1758,8 @@ export function testsForRpcMethodAssumingNoBlockParam(
   );
 
   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 };
@@ -1797,7 +1824,7 @@ export function testsForRpcMethodAssumingNoBlockParam(
           },
         );
 
-        await expect(promiseForResult).rejects.toThrow('not valid JSON');
+        await expect(promiseForResult).rejects.toThrow(errorMessage);
       });
     });
 
@@ -1929,6 +1956,9 @@ export function testsForRpcMethodAssumingNoBlockParam(
                     chainId,
                     endpointUrl: rpcUrl,
                     failoverEndpointUrl: 'https://failover.endpoint/',
+                    error: expect.objectContaining({
+                      message: expect.stringContaining(errorMessage),
+                    }),
                   },
                 );
               },
@@ -2024,6 +2054,9 @@ export function testsForRpcMethodAssumingNoBlockParam(
                 ).toHaveBeenNthCalledWith(2, {
                   chainId,
                   endpointUrl: 'https://failover.endpoint/',
+                  error: expect.objectContaining({
+                    message: expect.stringContaining(errorMessage),
+                  }),
                 });
               },
             );
@@ -2327,6 +2360,9 @@ export function testsForRpcMethodAssumingNoBlockParam(
                     chainId,
                     endpointUrl: rpcUrl,
                     failoverEndpointUrl: 'https://failover.endpoint/',
+                    error: expect.objectContaining({
+                      message: `request to ${rpcUrl} failed, reason: Failed to fetch`,
+                    }),
                   },
                 );
               },
@@ -2420,6 +2456,9 @@ export function testsForRpcMethodAssumingNoBlockParam(
                 ).toHaveBeenNthCalledWith(2, {
                   chainId,
                   endpointUrl: 'https://failover.endpoint/',
+                  error: expect.objectContaining({
+                    message: `request to https://failover.endpoint/ failed, reason: Failed to fetch`,
+                  }),
                 });
               },
             );