Skip to content

Fix concurrent currency updates#1687

Closed
bergeron wants to merge 1 commit intomainfrom
assets-controllers/concurrent-currency-updates
Closed

Fix concurrent currency updates#1687
bergeron wants to merge 1 commit intomainfrom
assets-controllers/concurrent-currency-updates

Conversation

@bergeron
Copy link
Copy Markdown
Contributor

@bergeron bergeron commented Sep 19, 2023

Explanation

There is a race condition when the CurrencyRateController's setNativeCurrency or setCurrentCurrency are called concurrently. The user is impacted when switching networks, then switching again before the first switch has finished its currency update. It leaves MM extension stuck with the native token and exchange rate for the wrong network. See video (easier to reproduce if you throttle Chrome to 3G network speed):

Screen.Recording.2023-09-19.at.9.16.33.AM.mov

There is already a mutex guarding this operation. But when the first update finishes, it sets pendingNativeCurrency and pendingCurrentCurrency to null. So when the second update gets its turn at the mutex, it has lost the pending state it was waiting to switch to. So the controller never switches to the new network.

My fix is to expand the scope of the mutex to also include the setting of the pending state.

This should be a non-breaking change. New arguments are added to updateExchangeRate, but it keeps the same behavior as before when no arguments are provided.

A unit test is added that reproduces the bug on the old code, and passes on the code.

References

User reports we suspect are related:

Changelog

@metamask/assets-controllers

  • FIXED: Race condition in CurrencyRateController when currencies are updated concurrently
  • CHANGED: CurrencyRateController.updateExchangeRate adds 2 arguments: newCurrentCurrency and newNativeCurrency

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

@bergeron bergeron requested a review from a team as a code owner September 19, 2023 14:49
Comment on lines +220 to +230
if (newCurrentCurrency !== undefined) {
this.update((state) => {
state.pendingCurrentCurrency = newCurrentCurrency;
});
}

if (newNativeCurrency !== undefined) {
this.update((state) => {
state.pendingNativeCurrency = newNativeCurrency;
});
}
Copy link
Copy Markdown
Contributor Author

@bergeron bergeron Sep 19, 2023

Choose a reason for hiding this comment

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

The pending state may look less required now that the values are available as function arguments. But the pending state may be needed to fix a related bug.

When switching networks, MM extension temporarily uses the symbol + conversion rate from the old network until the calls return for the new network. (More visible on slow internet):

Screen.Recording.2023-09-18.at.8.25.51.AM.mov

Balances are usually available before conversion rates, so I think clients should be consulting the pending state to know the correct native symbol, and not show fiat values until rates are available for the new network.

@bergeron
Copy link
Copy Markdown
Contributor Author

This may get superseded by this multichain refactor: #1805

jiexi added a commit to MetaMask/metamask-extension that referenced this pull request Nov 14, 2023
## **Description**

Bumps `@metamask/assets-controllers` to `v18.0.0` which includes
breaking changes for the CurrencyRateController. Notably the structure
of the state has changed significantly. Part of making the state change
so drastic (as opposed to making the refactor towards Multichain be as
small/incremental as possible), is that the usage of
`pendingNativeCurrency`, `pendingCurrentCurrency`, and how the
controller handles state change in general causes unexpected behavior
with concurrent updates. The update to CurrencyRateController also
solves this issue.

## **Related issues**

Fixes: MetaMask/MetaMask-planning#1025
Related: MetaMask/core#1687

## **Manual testing steps**

Nothing should change for the end user.

## **Screenshots/Recordings**

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

### **Before**

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

### **After**

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

## **Pre-merge author checklist**

- [x] I’ve followed [MetaMask Coding
Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md).
- [x] I've clearly explained what problem this PR is solving and how it
is solved.
- [x] I've linked related issues
- [ ] I've included manual testing steps
- [ ] I've included screenshots/recordings if applicable
- [ ] I’ve included tests if applicable
- [ ] 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-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)).
- [ ] I’ve properly set the pull request status:
  - [ ] In case it's not yet "ready for review", I've set it to "draft".
- [ ] In case it's "ready for review", I've changed it from "draft" to
"non-draft".

## **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.

---------

Co-authored-by: MetaMask Bot <metamaskbot@users.noreply.github.com>
Co-authored-by: Alex Donesky <adonesky@gmail.com>
@bergeron bergeron closed this Nov 28, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant