MetaMask / MetaMask/metamask-extension

[State Sync Redesign 2.5] Replace `this.store` with direct persistence subscriptions

Open
#40,056 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

INVALID-ISSUE-TEMPLATE team-extension-platform
Dominant language
TypeScript
Stars
13.2k
Forks
5.6k
Avg merge
2d 5h
Merged PRs (30d)
451

Description

`this.store` (ComposableObservableStore, `persist: true`) feeds the persistence manager in `background.js`. Replace with direct messenger subscriptions.

Current consumers in `background.js`:
1. `controller.store.getState()` → iterate controllers, derive persisted state
2. `controller.store.on('stateChange', ...)` → messenger subscription with `deriveStateFromMetadata`
3. `controller.store.config[key].metadata` → access via `controllersByName[key].metadata`
4. `controller.store.on('update', safePersist)` → call `safePersist` from each controller subscription

## Persistence architecture detail

- `OperationSafener` debounces writes at 1000ms (separate from the 200ms UI debounce)
- `PersistenceManager.persist()` uses `navigator.locks.request(STATE_LOCK)` with abort of prior writes if a newer one is pending
- Split-state storage (`platformSplitStateGradualRollout` remote flag) already writes per-controller keys via `#pendingPairs` → `local.set(toSet)`
- Known issue: "if the state is very large it *can* take more than the debounce's `wait` time to write, resulting in a pile up" (PersistenceManager comment)
- Vault backup (IndexedDB) for KeyringController, AppMetadataController, MetaMetricsController is a separate write path after main storage

**Constraint:** New messenger-based persistence subscriptions must preserve the existing 1000ms debounce and `navigator.locks` serialization. Do NOT write on every `stateChange` event — coalesce into `#pendingPairs` and let the existing `persist()` cycle handle it.

## Steps

1. **Dual-write first**: Wire new messenger-based persistence subscriptions alongside existing `this.store` subscriptions. New subscriptions feed into the same `PersistenceManager.#pendingPairs` and `persist()` cycle.
2. Validate via E2E that state persists correctly across extension restarts
3. Validate vault backup path (IndexedDB for KeyringController, AppMetadataController, MetaMetricsController)
4. Verify no write amplification: compare `chrome.storage.local.set` call frequency before/after
5. Remove `this.store` construction and the ~113-line controller list in `updateStructure`
6. Remove `this.store` consumers in `background.js`

## Files

- `app/scripts/background.js`
- `app/scripts/metamask-controller.js`
- `app/scripts/lib/stores/extension-store.ts`

## Risk

**Medium** — persistence is zero-tolerance for regressions. Dual-write validation before removal is mandatory. Write amplification must not increase.

## Dependencies

None (can proceed independently of other issues)

## Context

Part of [State Sync Redesign — Epic 2: Background cleanup].

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reading app/scripts/background.js, app/scripts/metamask-controller.js, and app/scripts/lib/stores/extension-store.ts, focusing on the listed store consumers and PersistenceManager's pending-pair and persist cycle. Validate dual-write behavior with E2E extension-restart checks and the specified IndexedDB vault backup controllers. Done means the store consumers and construction are removed without increasing chrome.storage.local.set frequency, while preserving the 1000ms debounce and navigator.locks serialization.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
backend
Issue type
Refactor
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.