Skip to content

chore(runway): cherry-pick fix: Initialize feature flags from state#21229

Merged
Cal-L merged 1 commit into
release/7.57.0from
cherry-pick-7-57-0-981f4e5
Oct 15, 2025
Merged

chore(runway): cherry-pick fix: Initialize feature flags from state#21229
Cal-L merged 1 commit into
release/7.57.0from
cherry-pick-7-57-0-981f4e5

Conversation

@runway-github

@runway-github runway-github Bot commented Oct 15, 2025

Copy link
Copy Markdown
Contributor

Description

0ee7360 introduced a change such that
when the app is cold-started and Engine is initialized,
RemoteFeatureFlagController is no longer instructed to reuse feature
flags from a previous start, but rather, it will fetch them all over
again. This change was made so that by simply listening to
RemoteFeatureFlagController:stateChange, the RPC failover mode in
NetworkController could be easily enabled or disabled.

In hindsight, this changed increased the start time of the app,
negatively impacting performance. This is particularly noticeable on
low-end devices where the app is frequently killed and restarted
automatically due to out-of-memory issues. Also, changing the behavior
of RemoteFeatureFlagController:stateChange in the way described above
is admittedly strange.

To address this, this PR once again initializes
RemoteFeatureFlagController from persisted state. As for
NetworkController, it now correctly asks for the
RemoteFeatureFlagController state once it is initialized and uses it to
switch the RPC failover mode.

Changelog

CHANGELOG entry: Don't re-fetch feature flags on app restart, but rely
on persistence instead

Related issues

(N/A)

Manual testing steps

  1. Check out this branch.
  2. Add QUICKNODE_ environment variables to .js.env. Ask me for what
    they should be.
  3. Check out this branch, run yarn setup:expo, run yarn watch:clean.
  4. Open
    node_modules/@metamask/network-controller/dist/rpc-service/rpc-service.cjs,
    look for async function _RpcService_processRequest and make these
    changes:
    async function _RpcService_processRequest(fetchOptions) {
        let response;
        try {

return await __classPrivateFieldGet(this, _RpcService_policy,
"f").execute(async () => {

  • console.log('[REQUEST]', this.endpointUrl.toString(), 'with',
    fetchOptions);
    •       if (
      
  • this.endpointUrl.toString().includes("linea-mainnet.infura.io") ||
  • this.endpointUrl.toString().includes("mainnet.era.zksync.io")
    •       ) {
      
  • console.log('[RESPONSE]', this.endpointUrl.toString(), '=> 502');
    •           throw new controller_utils_1.HttpError(502);
      
    •       }
      

response = await __classPrivateFieldGet(this, _RpcService_fetch,
"f").call(this, this.endpointUrl, fetchOptions);

  • console.log('[RESPONSE]', this.endpointUrl.toString(), '=>',
    response.status);
    if (!response.ok) {
    throw new controller_utils_1.HttpError(response.status);
    }
    return await response.json();
    });
    }
  1. Open app/core/Engine/controllers/network-controller-init.ts, look
    for new NetworkController, and make these changes:
            return {
              ...commonOptions,
              policyOptions: {
                maxRetries,
    -           maxConsecutiveFailures: (maxRetries + 1) * 7,
    +           maxConsecutiveFailures: (maxRetries + 1) * 4,
              },
            };
          },
          additionalDefaultNetworks,
        };

const networkController = new
NetworkController(networkControllerOptions);
```
6. Open the app, go through onboarding if needed.
7. Once on the home screen, switch to Linea.
8. Monitor the messages appearing in your terminal. Pretty quickly, you
should see a line that says [REQUEST] https://linea-mainnet.infura.io/v3/<api key>... with a line under this
is `[RESPONSE] https://linea-mainnet.infura.io/v3/ => 502`.
9. Pull down on the token list a few times to refresh it.
10. Eventually you should start to see lines in the terminal such as
`[REQUEST] https://.linea-mainnet.quiknode.pro/`. This is
an indicator that the RPC failover logic is still working.

Screenshots/Recordings

(N/A)

Before

After

Pre-merge author checklist

Pre-merge reviewer checklist

  • I've manually tested the PR (e.g. pull and build branch, run the
    app, test code being changed).
  • I confirm that this PR addresses all acceptance criteria described
    in the ticket it closes and includes the necessary testing evidence such
    as recordings and or screenshots.

Note

Initialize RemoteFeatureFlagController from persisted state and toggle NetworkController RPC failover based on the walletFrameworkRpcFailoverEnabled flag, with tests for initial and updated states.

  • Engine:
    • Initialize RemoteFeatureFlagController with persisted state.
    • Add toggleRpcFailover to enable/disable RPC failover via NetworkController.
    • Subscribe to RemoteFeatureFlagController:stateChange using toggleRpcFailover and invoke it once on init.
  • Tests (app/core/Engine/Engine.test.ts):
    • Mock ClientConfigApiService and spy on NetworkController.enableRpcFailover/disableRpcFailover.
    • Add tests verifying RPC failover toggles:
      • On init when flag is already enabled/disabled.
      • After a later remote flag fetch enabling/disabling the feature.

Written by Cursor Bugbot for commit b0f154d. This will update automatically on new commits. Configure here.

981f4e5

<!--
Please submit this PR as a draft initially.
Do not mark it as "Ready for review" until the template has been
completely filled out, and PR status checks have passed at least once.
-->

<!--
Write a short description of the changes included in this pull request,
also include relevant motivation and context. Have in mind the following
questions:
1. What is the reason for the change?
2. What is the improvement/solution?
-->

0ee7360 introduced a change such that
when the app is cold-started and Engine is initialized,
RemoteFeatureFlagController is no longer instructed to reuse feature
flags from a previous start, but rather, it will fetch them all over
again. This change was made so that by simply listening to
`RemoteFeatureFlagController:stateChange`, the RPC failover mode in
NetworkController could be easily enabled or disabled.

In hindsight, this changed increased the start time of the app,
negatively impacting performance. This is particularly noticeable on
low-end devices where the app is frequently killed and restarted
automatically due to out-of-memory issues. Also, changing the behavior
of `RemoteFeatureFlagController:stateChange` in the way described above
is admittedly strange.

To address this, this PR once again initializes
RemoteFeatureFlagController from persisted state. As for
NetworkController, it now correctly asks for the
RemoteFeatureFlagController state once it is initialized and uses it to
switch the RPC failover mode.

<!--
If this PR is not End-User-Facing and should not show up in the
CHANGELOG, you can choose to either:
1. Write `CHANGELOG entry: null`
2. Label with `no-changelog`

If this PR is End-User-Facing, please write a short User-Facing
description in the past tense like:
`CHANGELOG entry: Added a new tab for users to see their NFTs`
`CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker`

(This helps the Release Engineer do their job more quickly and
accurately)
-->

CHANGELOG entry: Don't re-fetch feature flags on app restart, but rely
on persistence instead

(N/A)

1. Check out this branch.
2. Add `QUICKNODE_` environment variables to `.js.env`. Ask me for what
they should be.
3. Check out this branch, run `yarn setup:expo`, run `yarn watch:clean`.
4. Open
`node_modules/@metamask/network-controller/dist/rpc-service/rpc-service.cjs`,
look for `async function _RpcService_processRequest` and make these
changes:
    ``` diff
    async function _RpcService_processRequest(fetchOptions) {
        let response;
        try {
return await __classPrivateFieldGet(this, _RpcService_policy,
"f").execute(async () => {
+ console.log('[REQUEST]', this.endpointUrl.toString(), 'with',
fetchOptions);
    +           if (
+ this.endpointUrl.toString().includes("linea-mainnet.infura.io") ||
+ this.endpointUrl.toString().includes("mainnet.era.zksync.io")
    +           ) {
+ console.log('[RESPONSE]', this.endpointUrl.toString(), '=> 502');
    +               throw new controller_utils_1.HttpError(502);
    +           }
response = await __classPrivateFieldGet(this, _RpcService_fetch,
"f").call(this, this.endpointUrl, fetchOptions);
+ console.log('[RESPONSE]', this.endpointUrl.toString(), '=>',
response.status);
                if (!response.ok) {
throw new controller_utils_1.HttpError(response.status);
                }
                return await response.json();
            });
        }
    ```
5. Open `app/core/Engine/controllers/network-controller-init.ts`, look
for `new NetworkController`, and make these changes:
    ``` diff
            return {
              ...commonOptions,
              policyOptions: {
                maxRetries,
    -           maxConsecutiveFailures: (maxRetries + 1) * 7,
    +           maxConsecutiveFailures: (maxRetries + 1) * 4,
              },
            };
          },
          additionalDefaultNetworks,
        };
const networkController = new
NetworkController(networkControllerOptions);
    ```
6. Open the app, go through onboarding if needed.
7. Once on the home screen, switch to Linea.
8. Monitor the messages appearing in your terminal. Pretty quickly, you
should see a line that says `[REQUEST]
https://linea-mainnet.infura.io/v3/<api key>...` with a line under this
is `[RESPONSE] https://linea-mainnet.infura.io/v3/<api key> => 502`.
9. Pull down on the token list a few times to refresh it.
10. Eventually you should start to see lines in the terminal such as
`[REQUEST] https://<name>.linea-mainnet.quiknode.pro/<api-key>`. This is
an indicator that the RPC failover logic is still working.

<!-- If applicable, add screenshots and/or recordings to visualize the
before and after of your change. -->

(N/A)

<!-- [screenshots/recordings] -->

<!-- [screenshots/recordings] -->

- [x] I’ve followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile
Coding
Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [x] I've completed the PR template to the best of my ability
- [x] I’ve included tests if applicable
- [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [x] I’ve applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.

- [ ] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [ ] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Initialize `RemoteFeatureFlagController` from persisted state and
toggle `NetworkController` RPC failover based on
`walletFrameworkRpcFailoverEnabled` at init and on updates, with new
tests.
>
> - **Engine initialization**:
>   - Initialize `RemoteFeatureFlagController` with persisted `state`.
> - Add `toggleRpcFailover` helper and subscribe to
`RemoteFeatureFlagController:stateChange` using selector
`state.remoteFeatureFlags.walletFrameworkRpcFailoverEnabled`.
> - Immediately toggle RPC failover on init based on persisted
`walletFrameworkRpcFailoverEnabled`.
> - **Tests (`app/core/Engine/Engine.test.ts`)**:
>   - Mock `ClientConfigApiService` and `NetworkController` methods.
> - Add tests to verify RPC failover is enabled/disabled on init per
existing flag.
> - Add async tests to verify RPC failover toggles when the feature flag
changes after fetch.
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
df7f8ed. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
@runway-github runway-github Bot requested a review from a team as a code owner October 15, 2025 19:37
@github-actions

Copy link
Copy Markdown
Contributor

CLA Signature Action: All authors have signed the CLA. You may need to manually re-run the blocking PR check if it doesn't pass in a few minutes.

@metamaskbot metamaskbot added the team-runway-bot-deprecated DEPRECATED: please use "team-bots" instead label Oct 15, 2025
Comment thread app/core/Engine/Engine.ts
Logger.log('Disabling RPC failover');
networkController.disableRpcFailover();
}
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Bug: RPC Failover Toggle Parameter Type Issue

The toggleRpcFailover function's isRpcFailoverEnabled parameter is typed as Json, which is too generic. Using this parameter directly in a boolean conditional can lead to incorrect RPC failover enabling or disabling due to unexpected type coercion.

Fix in Cursor Fix in Web

@mcmire mcmire Oct 15, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We have to do this because we don't know what type the feature flags are. We're basically checking whether the flag is truthy or falsy, which, while not completely exact, is good enough for our case.

@sonarqubecloud

Copy link
Copy Markdown

@Cal-L Cal-L left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

@Cal-L Cal-L merged commit d862b8b into release/7.57.0 Oct 15, 2025
113 of 115 checks passed
@Cal-L Cal-L deleted the cherry-pick-7-57-0-981f4e5 branch October 15, 2025 20:16
@github-actions github-actions Bot locked and limited conversation to collaborators Oct 15, 2025
@metamaskbot metamaskbot added the release-7.57.0 Issue or pull request that will be included in release 7.57.0 label Oct 15, 2025
@metamaskbot

Copy link
Copy Markdown
Collaborator

No release label on PR. Adding release label release-7.57.0 on PR, as PR was cherry-picked in branch 7.57.0.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

release-7.57.0 Issue or pull request that will be included in release 7.57.0 size-M team-runway-bot-deprecated DEPRECATED: please use "team-bots" instead

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants