Skip to content

Commit f91e609

Browse files
authored
chore: add segment error tracking to vault issues (MetaMask#19425)
## **Description** This PR standardizes vault corruption tracking across MetaMask Mobile by replacing inconsistent direct tracking calls with the centralized `trackVaultCorruption` utility in multiple components. **What is the reason for the change?** 1. **Inconsistent Tracking Implementation**: Multiple components (Login, LockScreen, App, Authentication, EngineService) were using different approaches to track vault corruption events 2. **Bypassed Utility Logic**: Some components used direct `track()` calls that bypassed the `trackVaultCorruption` utility's error filtering and MetaMetrics enabled state checking 3. **Missing Tracking Coverage**: Some critical vault corruption scenarios (lockscreen failures, app startup failures, engine initialization failures, migration issues) weren't being tracked at all 4. **Code Duplication**: Similar vault corruption tracking logic was duplicated across multiple files **What is the improvement/solution?** 1. **Standardized all vault corruption tracking** to use the `trackVaultCorruption` utility across Login, LockScreen, App, Authentication, EngineService, and migration components 2. **Added missing tracking coverage** for previously untracked vault corruption scenarios 3. **Ensured consistent error filtering** using the utility's `isVaultRelatedError()` function 4. **Proper MetaMetrics state checking** before tracking events 5. **Cleaned up debug logs** from the tracking utility ## **Analytics Tracking Information** **Event Name:** `Vault Corruption Detected` **Key Error Types to Monitor:** **Login & Authentication Failures:** - `vault_error` - Core vault unlock failures ("Cannot unlock without a previous vault.") - `json_parse_error` - Vault data corruption (JSON parsing issues) - `lockscreen_authentication_failure` - Lock screen unlock failures - `authentication_service_failure` - System authentication failures - `app_startup_authentication_failure` - App startup authentication issues **Vault Recovery & Handling:** - `vault_corruption_handling` - When vault recovery process is initiated - `vault_corruption_handling_failed` - When vault recovery attempts fail **System & Migration Issues:** - `engine_initialization_failure` - Core engine startup failures ("Error creating the vault") - `migration_missing_vault` - Existing users missing vault during app updates **Context Information:** - `login_authentication` - Errors during login flow - `vault_corruption_recovery_attempt` - During vault recovery process - `lockscreen_unlock_failed` - Lock screen specific failures - `app_initialization_unlock_failed` - App startup unlock issues - `engine_service_startup` - Engine initialization context - `migration_existing_user_validation` - Migration-specific issues - `app_triggered_auth_failed` - Authentication service failures **Additional Properties:** - `oauth_login` - Whether user was in OAuth/social login flow - `migration_number` - Specific migration version for migration errors - `has_existing_state` - Whether engine had existing state during initialization **Common Error Messages to Look For:** - "Cannot unlock without a previous vault." - Login vault unlock failures - "Error creating the vault" - Engine/system vault creation issues - "No vault in backup" - Backup restoration failures - "Password does not exist when calling SecureKeychain.getGenericPassword" - Keychain access failures - "Migration X: Existing user missing vault in KeyringController" - Migration vault issues **What to Look For:** - Spikes in vault corruption events across different components - Patterns in specific error types that indicate systemic issues - Migration-related vault corruption during app updates - Authentication failures that could indicate keychain/storage problems ## **Changelog** CHANGELOG entry: null ## **Related issues** Fixes: #[issue-number] ## **Manual testing steps** ```gherkin Feature: Standardized vault corruption tracking across components Scenario: Login component vault corruption tracking Given user attempts to login with corrupted vault When login fails with "Cannot unlock without a previous vault." Then vault corruption is tracked using trackVaultCorruption utility And event includes error_type="vault_error" and context="login_authentication" Scenario: LockScreen component vault corruption tracking Given user attempts to unlock from lock screen When unlock fails with vault/authentication errors Then vault corruption is tracked with error_type="lockscreen_authentication_failure" And uses same utility as other components Scenario: App startup vault corruption tracking Given app starts with corrupted vault state When authentication fails during startup Then vault corruption is tracked with error_type="app_startup_authentication_failure" And follows same tracking pattern as other components Scenario: Engine initialization vault corruption tracking Given engine fails to initialize due to vault issues When EngineService catches "Error creating the vault" errors Then vault corruption is tracked with error_type="engine_initialization_failure" And includes has_existing_state property Scenario: Migration vault corruption tracking Given existing user during app migration When migration detects missing vault Then vault corruption is tracked with error_type="migration_missing_vault" And includes migration_number property ``` ## **Screenshots/Recordings** <img width="1681" height="818" alt="image" src="https://github.com/user-attachments/assets/794380b6-150d-412d-9807-874c95c62542" /> <!-- This is an analytics standardization with no UI changes --> ### **Before** - Inconsistent vault corruption tracking across components - Some components missing vault corruption tracking entirely - Direct track calls bypassing utility's error filtering and state checking - Different tracking patterns and property structures across files ### **After** - Standardized `trackVaultCorruption` utility usage across all components - Comprehensive vault corruption tracking coverage - Consistent error filtering and MetaMetrics state checking - Uniform event structure and properties across all tracking points ## **Pre-merge author checklist** - [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. ## **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. --- **Technical Implementation Details:** **Files Modified:** - `app/components/Views/Login/index.tsx` - Replaced 3 direct `track()` calls with `trackVaultCorruption()` calls - `app/components/Views/LockScreen/index.js` - Added `trackVaultCorruption()` for lockscreen authentication failures - `app/components/Nav/App/App.tsx` - Added `trackVaultCorruption()` for app startup authentication failures - `app/core/Authentication/Authentication.ts` - Added `trackVaultCorruption()` for authentication service failures - `app/core/EngineService/EngineService.ts` - Added `trackVaultCorruption()` for engine initialization failures - `app/store/migrations/util/index.tsx` - Added `trackVaultCorruption()` for migration vault validation failures - `app/core/Analytics/MetaMetrics.events.ts` - Added `VAULT_CORRUPTION_DETECTED` event definition - `app/util/analytics/vaultCorruptionTracking.ts` - Removed debug `console.log` statements **Key Changes Made:** 1. **Login Component**: Replaced direct `track(MetaMetricsEvents.VAULT_CORRUPTION_DETECTED, ...)` calls with `trackVaultCorruption(...)` calls 2. **LockScreen Component**: Added new tracking for authentication failures during unlock 3. **App Component**: Added new tracking for app startup authentication failures 4. **Authentication Service**: Added new tracking for authentication service failures 5. **EngineService**: Added new tracking for engine initialization failures 6. **Migration Utility**: Added new tracking for existing users missing vault during migrations **Benefits:** - **Comprehensive Coverage**: All major vault corruption scenarios now tracked consistently - **Standardized Implementation**: Single utility function used across all components - **Proper State Checking**: MetaMetrics enabled state checked before all tracking - **Error Filtering**: All tracking benefits from `isVaultRelatedError()` filtering - **Maintainability**: Centralized tracking logic easier to maintain and update - **Analytics Consistency**: Uniform event structure across all tracking points <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Adds centralized vault corruption tracking utility and integrates it across login, lock screen, app startup, engine init, auth service, and migrations, plus defines event and tests. > > - **Analytics**: > - Add `trackVaultCorruption` utility with `isVaultRelatedError` in `app/util/analytics/vaultCorruptionTracking.ts` and comprehensive tests. > - Define new event `VAULT_CORRUPTION_DETECTED` in `app/core/Analytics/MetaMetrics.events.ts`. > - **UI/Flows**: > - `Login`: Track vault corruption on detection, handling attempts, and failures using `trackVaultCorruption`. > - `LockScreen`: Track auth failures during unlock. > - `Nav/App`: Track auth failures on app startup. > - **Core Services**: > - `Authentication.appTriggeredAuth`: Track authentication failures suggesting vault/keychain issues. > - `EngineService.start`: Track engine initialization failures and route to vault recovery. > - **Migrations**: > - Log and track when existing users are missing `KeyringController.vault` in migration validation. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit b2a9cc6. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 22dcd90 commit f91e609

9 files changed

Lines changed: 601 additions & 3 deletions

File tree

app/components/Nav/App/App.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ import { SmartAccountModal } from '../../Views/MultichainAccounts/AccountDetails
154154
import TradeWalletActions from '../../Views/TradeWalletActions';
155155
import { BIP44AccountPermissionWrapper } from '../../Views/MultichainAccounts/MultichainPermissionsSummary/BIP44AccountPermissionWrapper';
156156
import { useEmptyNavHeaderForConfirmations } from '../../Views/confirmations/hooks/ui/useEmptyNavHeaderForConfirmations';
157+
import { trackVaultCorruption } from '../../../util/analytics/vaultCorruptionTracking';
157158

158159
const clearStackNavigatorOptions = {
159160
headerShown: false,
@@ -1125,6 +1126,12 @@ const App: React.FC = () => {
11251126
const locked =
11261127
errorMessage === AUTHENTICATION_APP_TRIGGERED_AUTH_NO_CREDENTIALS;
11271128

1129+
// Track vault corruption with enabled state checking
1130+
trackVaultCorruption(errorMessage, {
1131+
error_type: 'app_startup_authentication_failure',
1132+
context: 'app_initialization_unlock_failed',
1133+
});
1134+
11281135
// Only call lockApp if there is an existing user to prevent unnecessary calls
11291136
await Authentication.lockApp({ reset: false, locked });
11301137
trackErrorAsAnalytics(

app/components/Views/LockScreen/index.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import { Authentication } from '../../../core';
77
import Routes from '../../../constants/navigation/Routes';
88
import { CommonActions } from '@react-navigation/native';
99
import trackErrorAsAnalytics from '../../../util/metrics/TrackError/trackErrorAsAnalytics';
10+
import { trackVaultCorruption } from '../../../util/analytics/vaultCorruptionTracking';
1011
import FoxLoader from '../../UI/FoxLoader';
11-
1212
/**
1313
* Main view component for the Lock screen
1414
*/
@@ -79,6 +79,15 @@ class LockScreen extends PureComponent {
7979
Logger.log('Lockscreen::unlockKeychain - authentication successful');
8080
} catch (error) {
8181
this.lock();
82+
83+
if (error?.message) {
84+
// Track vault corruption with enabled state checking
85+
trackVaultCorruption(error.message, {
86+
error_type: 'lockscreen_authentication_failure',
87+
context: 'lockscreen_unlock_failed',
88+
});
89+
}
90+
8291
trackErrorAsAnalytics(
8392
'Lockscreen: Authentication failed',
8493
error?.message,

app/components/Views/Login/index.tsx

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ import { containsErrorMessage } from '../../../util/errorHandling';
5555
import { MetaMetricsEvents } from '../../../core/Analytics';
5656
import { LoginViewSelectors } from '../../../../e2e/selectors/wallet/LoginView.selectors';
5757
import trackErrorAsAnalytics from '../../../util/metrics/TrackError/trackErrorAsAnalytics';
58+
import { trackVaultCorruption } from '../../../util/analytics/vaultCorruptionTracking';
5859
import { downloadStateLogs } from '../../../util/logs';
5960
import {
6061
trace,
@@ -279,6 +280,13 @@ const Login: React.FC<LoginProps> = ({ saveOnboardingEvent }) => {
279280
const handleVaultCorruption = async () => {
280281
const LOGIN_VAULT_CORRUPTION_TAG = 'Login/ handleVaultCorruption:';
281282

283+
// Track vault corruption handling attempt
284+
trackVaultCorruption(VAULT_ERROR, {
285+
error_type: 'vault_corruption_handling',
286+
context: 'vault_corruption_recovery_attempt',
287+
oauth_login: isComingFromOauthOnboarding,
288+
});
289+
282290
// No need to check password requirements here, it will be checked in onLogin
283291
try {
284292
setLoading(true);
@@ -314,8 +322,16 @@ const Login: React.FC<LoginProps> = ({ saveOnboardingEvent }) => {
314322
throw new Error(`${LOGIN_VAULT_CORRUPTION_TAG} ${backupResult.error}`);
315323
}
316324
} catch (e: unknown) {
325+
// Track vault corruption handling failure
326+
trackVaultCorruption((e as Error).message, {
327+
error_type: 'vault_corruption_handling_failed',
328+
context: 'vault_corruption_recovery_failed',
329+
oauth_login: isComingFromOauthOnboarding,
330+
});
331+
317332
Logger.error(e as Error);
318333
setLoading(false);
334+
319335
setError(strings('login.invalid_password'));
320336
}
321337
};
@@ -506,6 +522,7 @@ const Login: React.FC<LoginProps> = ({ saveOnboardingEvent }) => {
506522
}
507523

508524
setLoading(false);
525+
509526
setError(strings('login.invalid_password'));
510527
trackErrorAsAnalytics('Login: Invalid Password', loginErrorMessage);
511528
};
@@ -550,6 +567,15 @@ const Login: React.FC<LoginProps> = ({ saveOnboardingEvent }) => {
550567
containsErrorMessage(loginError, VAULT_ERROR) ||
551568
containsErrorMessage(loginError, JSON_PARSE_ERROR_UNEXPECTED_TOKEN)
552569
) {
570+
// Track vault corruption detected
571+
trackVaultCorruption(loginErrorMessage, {
572+
error_type: containsErrorMessage(loginError, VAULT_ERROR)
573+
? 'vault_error'
574+
: 'json_parse_error',
575+
context: 'login_authentication',
576+
oauth_login: isComingFromOauthOnboarding,
577+
});
578+
553579
await handleVaultCorruption();
554580
} else if (toLowerCaseEquals(loginErrorMessage, DENY_PIN_ERROR_ANDROID)) {
555581
updateBiometryChoice(false);

app/core/Analytics/MetaMetrics.events.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,7 @@ enum EVENT_NAME {
403403
CHANGE_DAPP_PERMISSIONS = 'Changed dapp permissions',
404404

405405
// Vault Corruption
406+
VAULT_CORRUPTION_DETECTED = 'Vault Corruption Detected',
406407
VAULT_CORRUPTION_RESTORE_WALLET_SCREEN_VIEWED = 'Vault Corruption Restore Wallet Screen Viewed',
407408
VAULT_CORRUPTION_RESTORE_WALLET_BUTTON_PRESSED = 'Vault Corruption Restore Wallet Button Pressed',
408409
VAULT_CORRUPTION_WALLET_SUCCESSFULLY_RESTORED_SCREEN_VIEWED = 'Vault Corruption Wallet Successfully Restored Screen Viewed',
@@ -1039,6 +1040,7 @@ const events = {
10391040
BROWSER_SWITCH_TAB: generateOpt(EVENT_NAME.BROWSER_SWITCH_TAB),
10401041

10411042
// Vault corruption
1043+
VAULT_CORRUPTION_DETECTED: generateOpt(EVENT_NAME.VAULT_CORRUPTION_DETECTED),
10421044
VAULT_CORRUPTION_RESTORE_WALLET_SCREEN_VIEWED: generateOpt(
10431045
EVENT_NAME.VAULT_CORRUPTION_RESTORE_WALLET_SCREEN_VIEWED,
10441046
),

app/core/Authentication/Authentication.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ import { toChecksumHexAddress } from '@metamask/controller-utils';
7272
import AccountTreeInitService from '../../multichain-accounts/AccountTreeInitService';
7373
import { renewSeedlessControllerRefreshTokens } from '../OAuthService/SeedlessControllerHelper';
7474
import { EntropySourceId } from '@metamask/keyring-api';
75+
import { trackVaultCorruption } from '../../util/analytics/vaultCorruptionTracking';
7576

7677
/**
7778
* Holds auth data used to determine auth configuration
@@ -621,7 +622,6 @@ class AuthenticationService {
621622
// TODO: Replace "any" with type
622623
// eslint-disable-next-line @typescript-eslint/no-explicit-any
623624
const credentials: any = await SecureKeychain.getGenericPassword();
624-
625625
const password = credentials?.password;
626626
if (!password) {
627627
throw new AuthenticationError(
@@ -658,10 +658,18 @@ class AuthenticationService {
658658
// TODO: Replace "any" with type
659659
// eslint-disable-next-line @typescript-eslint/no-explicit-any
660660
} catch (e: any) {
661+
const errorMessage = (e as Error).message;
662+
663+
// Track authentication failures that could indicate vault/keychain issues to Segment
664+
trackVaultCorruption(errorMessage, {
665+
error_type: 'authentication_service_failure',
666+
context: 'app_triggered_auth_failed',
667+
});
668+
661669
ReduxService.store.dispatch(authError(bioStateMachineId));
662670
!disableAutoLogout && this.lockApp({ reset: false });
663671
throw new AuthenticationError(
664-
(e as Error).message,
672+
errorMessage,
665673
AUTHENTICATION_APP_TRIGGERED_AUTH_ERROR,
666674
this.authData,
667675
);

app/core/EngineService/EngineService.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import NavigationService from '../NavigationService';
1818
import Routes from '../../constants/navigation/Routes';
1919
import { MetaMetrics } from '../Analytics';
2020
import { VaultBackupResult } from './types';
21+
import { trackVaultCorruption } from '../../util/analytics/vaultCorruptionTracking';
2122
import { INIT_BG_STATE_KEY, UPDATE_BG_STATE_KEY, LOG_TAG } from './constants';
2223

2324
export class EngineService {
@@ -83,6 +84,12 @@ export class EngineService {
8384
// `Engine.init()` call mutates `typeof UntypedEngine` to `TypedEngine`
8485
this.updateControllers(Engine as unknown as TypedEngine);
8586
} catch (error) {
87+
trackVaultCorruption((error as Error).message, {
88+
error_type: 'engine_initialization_failure',
89+
context: 'engine_service_startup',
90+
has_existing_state: Object.keys(state).length > 0,
91+
});
92+
8693
Logger.error(
8794
error as Error,
8895
'Failed to initialize Engine! Falling back to vault recovery.',

app/store/migrations/util/index.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { captureException } from '@sentry/react-native';
22
import { hasProperty, isObject } from '@metamask/utils';
33
import Logger from '../../../util/Logger';
4+
import { trackVaultCorruption } from '../../../util/analytics/vaultCorruptionTracking';
45

56
export interface ValidState {
67
engine: {
@@ -65,6 +66,16 @@ export function ensureValidState<T>(
6566
'Is vault defined at KeyringController at migration when existingUser',
6667
hasVault,
6768
);
69+
70+
// Track vault corruption if existing user has no vault
71+
if (!hasVault) {
72+
const errorMessage = `Migration ${migrationNumber}: Existing user missing vault in KeyringController`;
73+
trackVaultCorruption(errorMessage, {
74+
error_type: 'migration_missing_vault',
75+
context: 'migration_existing_user_validation',
76+
migration_number: migrationNumber,
77+
});
78+
}
6879
} catch (error) {
6980
Logger.error(
7081
new Error(

0 commit comments

Comments
 (0)