Skip to content

Commit

Permalink
validate post migration state
Browse files Browse the repository at this point in the history
  • Loading branch information
owencraston committed Jan 6, 2025
1 parent 196f7dc commit 8a10efc
Show file tree
Hide file tree
Showing 2 changed files with 116 additions and 0 deletions.
5 changes: 5 additions & 0 deletions app/store/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import persistConfig from './persistConfig';
import getUIStartupSpan from '../core/Performance/UIStartup';
import ReduxService from '../core/redux';
import { onPersistedDataLoaded } from '../actions/user';
import { validatePostMigrationState } from './validateState';

// TODO: Improve type safety by using real Action types instead of `any`
// TODO: Replace "any" with type
Expand Down Expand Up @@ -69,6 +70,10 @@ const createStoreAndPersistor = async () => {
endTrace({ name: TraceName.StoreInit });
// Signal that persisted data has been loaded
store.dispatch(onPersistedDataLoaded());

// validate the state after migration
const currentState = store.getState();
validatePostMigrationState(currentState);
};

persistor = persistStore(store, null, onPersistComplete);
Expand Down
111 changes: 111 additions & 0 deletions app/store/validateState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import Logger from '../util/Logger';
import { RootState } from '../reducers';
import { AccountsControllerState } from '@metamask/accounts-controller';

/**
* Each validation check is a function that:
* - Takes the final (post-migration) Redux state,
* - Returns an array of error strings if any validations fail or an empty array otherwise.
*/
type ValidationCheck = (state: RootState) => string[];

const LOG_TAG = 'MIGRATION_VALIDATE_STATE_ERROR';

/**
* Verifies that the engine is initialized
*/
const checkEngineInitialized: ValidationCheck = (state) => {
const errors: string[] = [];
if (!state.engine?.backgroundState) {
errors.push(`${LOG_TAG}: Engine backgroundState not found.`);
}
return errors;
};

/**
* Validates the AccountsControllerState to ensure required
* fields exist and match the expected structure.
*
* @param rootState - The Redux state to validate.
* @returns An array of error messages. If empty, the state is valid.
*/
const checkAccountsController: ValidationCheck = (rootState) => {
const errors: string[] = [];

// Safely access the AccountsController state in the engine backgroundState
const accountsState: AccountsControllerState | undefined =
rootState.engine?.backgroundState?.AccountsController;

// If it's missing altogether, return an error
if (!accountsState) {
errors.push(
`${LOG_TAG}: AccountsController state is missing in engine backgroundState.`,
);
return errors;
}

// 1. Check that internalAccounts exists
if (!accountsState.internalAccounts) {
errors.push(
`${LOG_TAG}: AccountsController No internalAccounts object found on AccountsControllerState.`,
);
return errors;
}

const { selectedAccount, accounts } = accountsState.internalAccounts;

// 2. Confirm there is at least one account
if (!accounts || Object.keys(accounts).length === 0) {
errors.push(
`${LOG_TAG}: AccountsController No accounts found in internalAccounts.accounts.`,
);
return errors;
}

// 3. Check that selectedAccount is non-empty
if (!selectedAccount) {
errors.push(
`${LOG_TAG}: AccountsController selectedAccount is missing or empty.`,
);
return errors;
}

// 4. Confirm the selectedAccount ID exists in internalAccounts.accounts
if (!accounts[selectedAccount]) {
errors.push(
`${LOG_TAG}: AccountsController The selectedAccount '${selectedAccount}' does not exist in the accounts record.`,
);
}

return errors;
};

/**
* If you have more checks, simply define them above and add them to this array.
*/
const checks: ValidationCheck[] = [
checkEngineInitialized,
checkAccountsController,
];

/**
* Runs all validations and logs any errors, but doesn’t throw.
* This makes sure your app keeps running even if some data is unexpected.
*/
export function validatePostMigrationState(state: RootState): void {
const allErrors: string[] = [];

for (const check of checks) {
const errors = check(state);
if (errors.length > 0) {
allErrors.push(...errors);
}
}

// If there are any errors, log them
if (allErrors.length > 0) {
Logger.error(new Error('Migration validation errors'), {
message: `State validation found these issues: ${allErrors.join(', ')}`,
});
}
}

0 comments on commit 8a10efc

Please sign in to comment.