Skip to content

Commit

Permalink
Add migration v3 (#977)
Browse files Browse the repository at this point in the history
* fix broken migrated flag

* add v3 migration to fix remove damaged flag from ok wallets

* improve codepush version tagging for sentry

* rename key to be consistent with previous naming

* add logging and fix missing await

* check for key first, the migrated flag
  • Loading branch information
brunobar79 committed Aug 4, 2020
1 parent 7576f5c commit f226873
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 33 deletions.
6 changes: 4 additions & 2 deletions src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,11 @@ if (__DEV__) {
initSentry({ dsn: SENTRY_ENDPOINT, environment: SENTRY_ENVIRONMENT });
}

CodePush.getUpdateMetadata().then(update => {
CodePush.getUpdateMetadata(CodePush.UpdateState.RUNNING).then(update => {
if (update) {
setRelease(`me.rainbow-${update.appVersion}-codepush:${update.label}`);
setRelease(
`me.rainbow-${VersionNumber.appVersion}-codepush:${update.label}`
);
} else {
setRelease(`me.rainbow-${VersionNumber.appVersion}`);
}
Expand Down
61 changes: 59 additions & 2 deletions src/model/migrations.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
import { findKey } from 'lodash';
import { findKey, keys } from 'lodash';
import {
getMigrationVersion,
setMigrationVersion,
} from '../handlers/localstorage/migrations';
import WalletTypes from '../helpers/walletTypes';
import { DEFAULT_WALLET_NAME, loadAddress, saveAddress } from '../model/wallet';
import {
DEFAULT_WALLET_NAME,
loadAddress,
oldSeedPhraseMigratedKey,
saveAddress,
seedPhraseKey,
} from '../model/wallet';
import store from '../redux/store';

import { walletsSetSelected, walletsUpdate } from '../redux/wallets';
import { hasKey } from './keychain';
import { colors } from '@rainbow-me/styles';
import logger from 'logger';

Expand Down Expand Up @@ -145,6 +152,56 @@ export default async function runMigrations() {
};

migrations.push(v2);
/*
*************** Migration v3 ******************
* This step makes sure there are no wallets marked as damaged
* incorrectly by the keychain integrity checks
*/
const v3 = async () => {
logger.sentry('Start migration v3');
const { wallets, selected } = store.getState().wallets;

if (!wallets) {
logger.sentry('Complete migration v3 early');
return;
}

const hasMigratedFlag = await hasKey(oldSeedPhraseMigratedKey);
if (!hasMigratedFlag) {
logger.sentry('Migration flag not set');
const hasOldSeedphraseKey = await hasKey(seedPhraseKey);
if (hasOldSeedphraseKey) {
logger.sentry('Old seedphrase is still there');
let incorrectDamagedWalletId = null;
const updatedWallets = { ...wallets };
keys(updatedWallets).forEach(walletId => {
if (
updatedWallets[walletId].damaged &&
!updatedWallets[walletId].imported
) {
logger.sentry('found incorrect damaged wallet', walletId);
delete updatedWallets[walletId].damaged;
incorrectDamagedWalletId = walletId;
}
});
logger.sentry('updating all wallets');
await store.dispatch(walletsUpdate(updatedWallets));
logger.sentry('done updating all wallets');
// Additionally, we need to check if it's the selected wallet
// and if that's the case, update it too
if (selected.id === incorrectDamagedWalletId) {
logger.sentry('need to update the selected wallet');
const updatedSelectedWallet =
updatedWallets[incorrectDamagedWalletId];
await store.dispatch(walletsSetSelected(updatedSelectedWallet));
logger.sentry('selected wallet updated');
}
}
}
logger.sentry('Complete migration v3');
};

migrations.push(v3);

logger.sentry(
`Migrations: ready to run migrations starting on number ${currentVersion}`
Expand Down
90 changes: 62 additions & 28 deletions src/model/wallet.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { captureException } from '@sentry/react-native';
import { captureException, captureMessage } from '@sentry/react-native';
import { signTypedData_v4, signTypedDataLegacy } from 'eth-sig-util';
import { isValidAddress, toBuffer } from 'ethereumjs-util';
import { ethers } from 'ethers';
Expand Down Expand Up @@ -33,7 +33,8 @@ export const privateKeyKey = 'rainbowPrivateKey';
export const addressKey = 'rainbowAddressKey';
export const selectedWalletKey = 'rainbowSelectedWalletKey';
export const allWalletsKey = 'rainbowAllWalletsKey';
export const seedPhraseMigratedKey = 'rainbowSeedPhraseMigratedKey';
export const oldSeedPhraseMigratedKey = 'rainbowOldSeedPhraseMigratedKey';
export const seedPhraseMigratedKey = 'rainbowSeedPhraseMigratedKey'; // NOT USED ANYMORE!

const privateKeyVersion = 1.0;
const seedPhraseVersion = 1.0;
Expand Down Expand Up @@ -255,7 +256,7 @@ const loadPrivateKey = async (
) => {
try {
const isSeedPhraseMigrated = await keychain.loadString(
seedPhraseMigratedKey
oldSeedPhraseMigratedKey
);

// We need to migrate the seedphrase & private key first
Expand Down Expand Up @@ -414,14 +415,6 @@ export const createWallet = async (
await savePrivateKey(wallet.address, wallet.privateKey);
logger.sentry('[createWallet] - saved private key');

// Save migration flag
await keychain.saveString(
seedPhraseMigratedKey,
'true',
publicAccessControlOptions
);
logger.sentry('[createWallet] - saved seed phrase migrated key');

addresses.push({
address: wallet.address,
avatar: null,
Expand Down Expand Up @@ -682,7 +675,7 @@ export const getAllWallets = async () => {
export const generateAccount = async (id, index) => {
try {
const isSeedPhraseMigrated = await keychain.loadString(
seedPhraseMigratedKey
oldSeedPhraseMigratedKey
);
let seedPhrase, hdnode;
// We need to migrate the seedphrase & private key first
Expand Down Expand Up @@ -717,19 +710,26 @@ export const generateAccount = async (id, index) => {

const migrateSecrets = async () => {
try {
logger.sentry('migrating secrets!');
const seedPhrase = await oldLoadSeedPhrase();

if (!seedPhrase) {
logger.sentry('old seed doesnt exist!');
// Save the migration flag to prevent this flow in the future
await keychain.saveString(
seedPhraseMigratedKey,
oldSeedPhraseMigratedKey,
'true',
publicAccessControlOptions
);
logger.sentry(
'Saved the migration flag to prevent this flow in the future'
);
return null;
}

logger.sentry('Got secret, now idenfifying wallet type');
const type = identifyWalletType(seedPhrase);
logger.sentry('Got type: ', type);
let hdnode, node, existingAccount;
switch (type) {
case WalletTypes.privateKey:
Expand All @@ -745,21 +745,38 @@ const migrateSecrets = async () => {
}

if (!existingAccount) {
logger.sentry('No existing account, so we have to derive it');
node = hdnode.derivePath(`${DEFAULT_HD_PATH}/0`);
existingAccount = new ethers.Wallet(node.privateKey);
logger.sentry('Got existing account');
}

// Save the private key in the new format
await savePrivateKey(existingAccount.address, existingAccount.privateKey);
// Check that wasn't migrated already!
const pkeyExists = await keychain.hasKey(
`${existingAccount.address}_${privateKeyKey}`
);
if (!pkeyExists) {
logger.sentry('new pkey didnt exist so we should save it');
// Save the private key in the new format
await savePrivateKey(existingAccount.address, existingAccount.privateKey);
logger.sentry('new pkey saved');
}
const { wallet } = await getSelectedWallet();

// Save the seedphrase in the new format
await saveSeedPhrase(seedPhrase, wallet.id);
const seedExists = await keychain.hasKey(`${wallet.id}_${seedPhraseKey}`);
if (!seedExists) {
logger.sentry('new seed didnt exist so we should save it');
await saveSeedPhrase(seedPhrase, wallet.id);
logger.sentry('new seed saved');
}
// Save the migration flag to prevent this flow in the future
await keychain.saveString(
seedPhraseMigratedKey,
oldSeedPhraseMigratedKey,
'true',
publicAccessControlOptions
);
logger.sentry('saved migrated key');
return {
hdnode,
privateKey: existingAccount.privateKey,
Expand All @@ -774,21 +791,38 @@ const migrateSecrets = async () => {

export const loadSeedPhraseAndMigrateIfNeeded = async id => {
try {
const isSeedPhraseMigrated = await keychain.loadString(
seedPhraseMigratedKey
);

// We need to migrate the seedphrase & private key first
// In that case we regenerate the existing private key to store it with the new format
let seedPhrase = null;
if (!isSeedPhraseMigrated) {
const migratedSecrets = await migrateSecrets();
seedPhrase = migratedSecrets?.seedPhrase;
}
// First we need to check if that key already exists
const keyFound = await keychain.loadString(`${id}_${seedPhraseKey}`);
if (!keyFound) {
logger.sentry('key not found, we should have a migration pending...');
// if it doesn't we might have a migration pending
const isSeedPhraseMigrated = await keychain.loadString(
oldSeedPhraseMigratedKey
);

if (!seedPhrase) {
logger.sentry('Migration pending?', !isSeedPhraseMigrated);

// We need to migrate the seedphrase & private key first
// In that case we regenerate the existing private key to store it with the new format
if (!isSeedPhraseMigrated) {
const migratedSecrets = await migrateSecrets();
seedPhrase = migratedSecrets?.seedPhrase;
} else {
logger.sentry('Migrated flag was set but there is no key!', id);
captureMessage('Missing seed for wallet');
}
} else {
logger.sentry('Getting seed directly');
const seedData = await getSeedPhrase(id);
seedPhrase = seedData?.seedphrase;
if (seedPhrase) {
logger.sentry('got seed succesfully');
} else {
captureMessage(
'Missing seed for wallet - (Key exists but value isnt valid)!'
);
}
}

return seedPhrase;
Expand Down
14 changes: 13 additions & 1 deletion src/redux/wallets.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
getAllWallets,
getSelectedWallet,
loadAddress,
oldSeedPhraseMigratedKey,
saveAddress,
saveAllWallets,
seedPhraseKey,
Expand Down Expand Up @@ -200,10 +201,21 @@ export const checkKeychainIntegrity = () => async (dispatch, getState) => {

const hasMigratedFlag = await hasKey(seedPhraseMigratedKey);
if (hasMigratedFlag) {
logger.sentry(
'[KeychainIntegrityCheck]: previous migrated flag is OK [NO LONGER RELEVANT]'
);
} else {
logger.sentry(
`[KeychainIntegrityCheck]: migrated flag is present: ${hasMigratedFlag} [NO LONGER RELEVANT]`
);
}

const hasOldSeedPhraseMigratedKey = await hasKey(oldSeedPhraseMigratedKey);
if (hasOldSeedPhraseMigratedKey) {
logger.sentry('[KeychainIntegrityCheck]: migrated flag is OK');
} else {
logger.sentry(
`[KeychainIntegrityCheck]: migrated flag is missing: ${hasMigratedFlag}`
`[KeychainIntegrityCheck]: migrated flag is present: ${hasOldSeedPhraseMigratedKey}`
);
}

Expand Down

0 comments on commit f226873

Please sign in to comment.