metamask-extension
metamask-extension copied to clipboard
feat(restore): Restore vault when state corruption occurs
Description
This pull request adds an automatic saving of the encrypted keyring vault to localStorage, as well as the last good migration number seen. We persist the lastGoodMigration key after migrator runs so it should always be up to date. We compare the value to the persisted state before running the migrator as another way of detecting a corrupted state tree.
We can, in the case of corruption, load the vault from localStorage and update the initialStateTree with that value (as well as onboarding completed to avoid a routing loop) and the extension can recover.
CONS: any third party optin preferences, metametrics, etc will be wiped and the onboarding flow has been bypassed. there's probably a cleaner way to avoid the loop but without this I just keep getting routed back to a full screen onboarding-type flow with an unlock screen. Typing password and submitting just lands me back on that page.
edit At @danjm request/suggestion I added an error handler for this state that will show the MetaMask error screen when the state restore happens. Screeenshots included below.
Related issues
Fixes:
Manual testing steps
- Run
yarn start
- Load the extension
- Unlock your wallet
- On the background process, inspect and go to application tab.
- Inspect local storage.
- See you have a 'lastGoodMigration' and 'metaMaskVault' key saved.
- In the console run
chrome.storage.local.set({ data: null, meta: null });
- Restart the extension
- Load the extension.
- See the error message in the screenshots section.
- Click restart
- Load extension
- See your accounts are set, but you'll get all notifications and etc as a new user would.
Screenshots/Recordings
Before
After
Pre-merge author checklist
- [ ] I’ve followed MetaMask Coding Standards.
- [ ] I've clearly explained what problem this PR is solving and how it is solved.
- [ ] 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 format if applicable
- [ ] I’ve applied the right labels on the PR (see labeling guidelines). Not required for external contributors.
- [ ] 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.
Codecov Report
Attention: Patch coverage is 57.14286%
with 24 lines
in your changes are missing coverage. Please review.
Project coverage is 68.69%. Comparing base (
c0dfd05
) to head (8deb78a
). Report is 64 commits behind head on develop.
Files | Patch % | Lines |
---|---|---|
app/scripts/lib/local-store.js | 56.76% | 16 Missing :warning: |
shared/lib/error-utils.js | 33.33% | 8 Missing :warning: |
Additional details and impacted files
@@ Coverage Diff @@
## develop #23056 +/- ##
===========================================
- Coverage 68.73% 68.69% -0.04%
===========================================
Files 1128 1128
Lines 43544 43605 +61
Branches 11643 11657 +14
===========================================
+ Hits 29929 29953 +24
- Misses 13615 13652 +37
:umbrella: View full report in Codecov by Sentry.
:loudspeaker: Have feedback on the report? Share it here.
Builds ready [234c4ab]
- builds: chrome, firefox
- builds (beta): chrome
- builds (flask): chrome, firefox
- builds (MMI): chrome, firefox
- builds (test): chrome, firefox
- builds (test-flask): chrome, firefox
- build viz: Build System
- mv3: Background Module Init Stats
- mv3: UI Init Stats
- mv3: Module Load Stats
- mv3: Bundle Size Stats
- mv2: E2e Actions Stats
- code coverage: Report
- storybook: Storybook
- typescript migration: Dashboard
- all artifacts
Page Load Metrics (1108 ± 45 ms)
Platform | Page | Metric | Min (ms) | Max (ms) | Average (ms) | StandardDeviation (ms) | MarginOfError (ms) |
---|---|---|---|---|---|---|---|
Chrome | Home | firstPaint | 114 | 359 | 198 | 64 | 31 |
domContentLoaded | 13 | 100 | 38 | 24 | 11 | ||
load | 891 | 1318 | 1108 | 94 | 45 | ||
domInteractive | 13 | 100 | 38 | 24 | 11 |
Bundle size diffs [🚨 Warning! Bundle size has increased!]
- background: 533 Bytes (0.02%)
- ui: 0 Bytes (0.00%)
- common: 0 Bytes (0.00%)
@Gudahtt do you think the current method of persisting the vault is sufficient? It currently updates it everytime the entire state tree changes. I could either do a check to see if the value has changed and avoid setting the state again or attempt to identify only the areas in Metamask-controller where the vault would change (addAccount? etc?) and persist it there instead. however keying on the state-persisted event doesn't have any of the side effects I was describing about impacting UI speed.
We could add a listener to the KeyringController directly, with a selector just for the vault. e.g. something like
messenger.on(
'KeyringController:stateChange',
(vault) => { ... },
(state) => state.vault
)
Alright i'll work on that today, plus removing the replacement of state when the lastGoodMigration doesn't match up.
Just as an aside
messenger.on( 'KeyringController:stateChange', (vault) => { ... }, (state) => state.vault )
This is dope.
Builds ready [30a9172]
- builds: chrome, firefox
- builds (beta): chrome
- builds (flask): chrome, firefox
- builds (MMI): chrome, firefox
- builds (test): chrome, firefox
- builds (test-flask): chrome, firefox
- build viz: Build System
- mv3: Background Module Init Stats
- mv3: UI Init Stats
- mv3: Module Load Stats
- mv3: Bundle Size Stats
- mv2: E2e Actions Stats
- code coverage: Report
- storybook: Storybook
- typescript migration: Dashboard
- all artifacts
Page Load Metrics (1212 ± 461 ms)
Platform | Page | Metric | Min (ms) | Max (ms) | Average (ms) | StandardDeviation (ms) | MarginOfError (ms) |
---|---|---|---|---|---|---|---|
Chrome | Home | firstPaint | 75 | 197 | 130 | 39 | 19 |
domContentLoaded | 10 | 61 | 32 | 15 | 7 | ||
load | 61 | 2533 | 1212 | 959 | 461 | ||
domInteractive | 10 | 61 | 32 | 15 | 7 |
Bundle size diffs [🚨 Warning! Bundle size has increased!]
- background: 924 Bytes (0.02%)
- ui: 215 Bytes (0.00%)
- common: 536 Bytes (0.01%)
Builds ready [08b8a8b]
- builds: chrome, firefox
- builds (beta): chrome
- builds (flask): chrome, firefox
- builds (MMI): chrome, firefox
- builds (test): chrome, firefox
- builds (test-flask): chrome, firefox
- build viz: Build System
- mv3: Background Module Init Stats
- mv3: UI Init Stats
- mv3: Module Load Stats
- mv3: Bundle Size Stats
- mv2: E2e Actions Stats
- code coverage: Report
- storybook: Storybook
- typescript migration: Dashboard
- all artifacts
Page Load Metrics (1192 ± 429 ms)
Platform | Page | Metric | Min (ms) | Max (ms) | Average (ms) | StandardDeviation (ms) | MarginOfError (ms) |
---|---|---|---|---|---|---|---|
Chrome | Home | firstPaint | 78 | 190 | 129 | 32 | 15 |
domContentLoaded | 11 | 68 | 31 | 17 | 8 | ||
load | 64 | 2129 | 1192 | 893 | 429 | ||
domInteractive | 11 | 68 | 31 | 17 | 8 |
Bundle size diffs [🚨 Warning! Bundle size has increased!]
- background: 924 Bytes (0.02%)
- ui: 215 Bytes (0.00%)
- common: 642 Bytes (0.01%)
- [ ] Needs copy updates (e.g. make sure they understand before they click restore that they are opting out of being able to get their data back)
- [ ] Needs e2e tests (e.g. covering the error screen being shown, store a state tree in the background, restart, recognize state corruption, and hit the error screen)
This PR has been automatically marked as stale because it has not had recent activity in the last 60 days. It will be closed in 14 days. Thank you for your contributions.
This PR was closed because there has been no follow up activity in the last 14 days. Thank you for your contributions.