Skip to content

Commit c856b75

Browse files
authored
Revert "Continue to retry failed requests when offline (#8623)" (#8739)
## Explanation <!-- Thanks for your contribution! Take a moment to answer these questions so that reviewers have the information they need to properly understand your changes: * What is the current state of things and why does it need to change? * What is the solution your changes offer and how does it work? * Are there any changes whose purpose might not obvious to those unfamiliar with the domain? * If your primary goal was to update one package but you found you had to update another one along the way, why did you do so? * If you had to upgrade a dependency, why did you do so? --> This reverts commit 0caca27. This PR is faulty, because although it does restore retries for failed requests, it also causes `onDegraded` and `onBreak` to be fired for `RpcServiceChain`, and hence causes `NetworkController:rpcEndpointChainDegraded` and `NetworkController:rpcEndpointChainUnavailable` to be fired as well. ## References <!-- Are there any issues that this pull request is tied to? Are there other links that reviewers should consult to understand these changes better? Are there client or consumer pull requests to adopt any breaking changes? For example: * Fixes #12345 * Related to #67890 --> ## 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 communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes `RpcService` retry/circuit-breaker behavior based on offline status, which can affect request reliability and when network degraded/unavailable events fire. Risk is mitigated by expanded unit/integration tests for offline scenarios. > > **Overview** > Reverts the previous behavior that continued retrying RPC requests while the user is offline by making `RpcService`'s retry filter short-circuit when `isOffline()` is true. > > This prevents offline connection failures from triggering retries, circuit breaks, and downstream NetworkController retry/degraded/unavailable event emissions, with updated/added tests covering offline unavailable and degraded scenarios and updated ESLint suppression metadata. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 263e1ca. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 381abeb commit c856b75

5 files changed

Lines changed: 338 additions & 141 deletions

File tree

eslint-suppressions.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1523,7 +1523,7 @@
15231523
},
15241524
"packages/network-controller/src/rpc-service/rpc-service.ts": {
15251525
"no-restricted-syntax": {
1526-
"count": 2
1526+
"count": 3
15271527
}
15281528
},
15291529
"packages/network-controller/tests/NetworkController.provider.test.ts": {

packages/network-controller/CHANGELOG.md

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
5151
- Deprecate `AbstractRpcService` and `RpcServiceRequestable` ([#8475](https://github.com/MetaMask/core/pull/8475))
5252
- There are no equivalents to these interfaces. If you need to take an "RPC-service-like" argument, it's best to declare which properties you're interested in rather than accepting the entire RPC service interface.
5353

54-
### Fixed
55-
56-
- Restore retries for failed requests even if the user is offline, preventing a bad user experience if the reported offline status is inaccurate or stuck ([#8623](https://github.com/MetaMask/core/pull/8623))
57-
5854
## [30.0.1]
5955

6056
### Changed

packages/network-controller/src/create-network-client-tests/rpc-endpoint-events.test.ts

Lines changed: 147 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,81 @@ describe('createNetworkClient - RPC endpoint events', () => {
266266
);
267267
});
268268

269-
it('does not publish the NetworkController:rpcEndpointUnavailable event when user is offline', async () => {
269+
it('does not retry requests when user is offline', async () => {
270+
const failoverEndpointUrl = 'https://failover.endpoint/';
271+
const request = {
272+
method: 'eth_gasPrice',
273+
params: [],
274+
};
275+
const expectedError = createResourceUnavailableError(503);
276+
277+
await withMockedCommunications(
278+
{ providerType: networkClientType },
279+
async (primaryComms) => {
280+
await withMockedCommunications(
281+
{
282+
providerType: 'custom',
283+
customRpcUrl: failoverEndpointUrl,
284+
},
285+
async () => {
286+
// Mock only one failure - if retries were happening, we'd need more
287+
primaryComms.mockRpcCall({
288+
request: {
289+
method: 'eth_blockNumber',
290+
params: [],
291+
},
292+
times: 1,
293+
response: {
294+
httpStatus: 503,
295+
},
296+
});
297+
298+
const rootMessenger = buildRootMessenger({
299+
connectivityStatus: CONNECTIVITY_STATUSES.Offline,
300+
});
301+
302+
const rpcEndpointRetriedEventHandler = jest.fn();
303+
rootMessenger.subscribe(
304+
'NetworkController:rpcEndpointRetried',
305+
rpcEndpointRetriedEventHandler,
306+
);
307+
308+
await withNetworkClient(
309+
{
310+
providerType: networkClientType,
311+
networkClientId: 'AAAA-AAAA-AAAA-AAAA',
312+
isRpcFailoverEnabled: true,
313+
failoverRpcUrls: [failoverEndpointUrl],
314+
messenger: rootMessenger,
315+
getRpcServiceOptions: () => ({
316+
fetch,
317+
btoa,
318+
policyOptions: {
319+
backoff: new ConstantBackoff(backoffDuration),
320+
},
321+
isOffline: (): boolean => false,
322+
}),
323+
},
324+
async ({ makeRpcCall }) => {
325+
// When offline, errors are not retried, so the request
326+
// should fail immediately without retries
327+
await expect(makeRpcCall(request)).rejects.toThrow(
328+
expectedError,
329+
);
330+
331+
// Verify that retry event was not published
332+
expect(
333+
rpcEndpointRetriedEventHandler,
334+
).not.toHaveBeenCalled();
335+
},
336+
);
337+
},
338+
);
339+
},
340+
);
341+
});
342+
343+
it('suppresses the NetworkController:rpcEndpointUnavailable event when user is offline', async () => {
270344
const failoverEndpointUrl = 'https://failover.endpoint/';
271345
const request = {
272346
method: 'eth_gasPrice',
@@ -1300,7 +1374,78 @@ describe('createNetworkClient - RPC endpoint events', () => {
13001374
);
13011375
});
13021376

1303-
it('does not publish the NetworkController:rpcEndpointDegraded event when user is offline', async () => {
1377+
it('does not retry requests when user is offline (degraded scenario)', async () => {
1378+
const request = {
1379+
method: 'eth_gasPrice',
1380+
params: [],
1381+
};
1382+
const expectedError = createResourceUnavailableError(503);
1383+
1384+
await withMockedCommunications(
1385+
{ providerType: networkClientType },
1386+
async (comms) => {
1387+
// Mock only one failure - if retries were happening, we'd need more
1388+
comms.mockRpcCall({
1389+
request: {
1390+
method: 'eth_blockNumber',
1391+
params: [],
1392+
},
1393+
times: 1,
1394+
response: {
1395+
httpStatus: 503,
1396+
},
1397+
});
1398+
comms.mockRpcCall({
1399+
request: {
1400+
method: 'eth_gasPrice',
1401+
params: [],
1402+
},
1403+
times: 1,
1404+
response: {
1405+
httpStatus: 503,
1406+
},
1407+
});
1408+
1409+
const rootMessenger = buildRootMessenger({
1410+
connectivityStatus: CONNECTIVITY_STATUSES.Offline,
1411+
});
1412+
1413+
const rpcEndpointRetriedEventHandler = jest.fn();
1414+
rootMessenger.subscribe(
1415+
'NetworkController:rpcEndpointRetried',
1416+
rpcEndpointRetriedEventHandler,
1417+
);
1418+
1419+
await withNetworkClient(
1420+
{
1421+
providerType: networkClientType,
1422+
networkClientId: 'AAAA-AAAA-AAAA-AAAA',
1423+
messenger: rootMessenger,
1424+
getRpcServiceOptions: () => ({
1425+
fetch,
1426+
btoa,
1427+
policyOptions: {
1428+
backoff: new ConstantBackoff(backoffDuration),
1429+
},
1430+
isOffline: (): boolean => false,
1431+
}),
1432+
},
1433+
async ({ makeRpcCall }) => {
1434+
// When offline, errors are not retried, so the request
1435+
// should fail immediately without retries
1436+
await expect(makeRpcCall(request)).rejects.toThrow(
1437+
expectedError,
1438+
);
1439+
1440+
// Verify that retry event was not published
1441+
expect(rpcEndpointRetriedEventHandler).not.toHaveBeenCalled();
1442+
},
1443+
);
1444+
},
1445+
);
1446+
});
1447+
1448+
it('suppresses the NetworkController:rpcEndpointDegraded event when user is offline', async () => {
13041449
const request = {
13051450
method: 'eth_gasPrice',
13061451
params: [],

0 commit comments

Comments
 (0)