github.com/status-im/status-go@v1.1.0/api/old_mobile_user_upgrading_from_v1_to_v2_test.go (about) 1 package api 2 3 import ( 4 "context" 5 "database/sql" 6 "strings" 7 "testing" 8 9 d_common "github.com/status-im/status-go/common" 10 11 "github.com/status-im/status-go/appdatabase" 12 "github.com/status-im/status-go/common/dbsetup" 13 "github.com/status-im/status-go/sqlite" 14 15 "github.com/stretchr/testify/suite" 16 17 "github.com/status-im/status-go/eth-node/types" 18 "github.com/status-im/status-go/multiaccounts/accounts" 19 "github.com/status-im/status-go/multiaccounts/common" 20 "github.com/status-im/status-go/protocol/tt" 21 "github.com/status-im/status-go/t/utils" 22 ) 23 24 const ( 25 oldMobileUserKeyUID = "0x855ab0a932e5325daab7a550b9fcd78d2a17de5e2b7a52241f82505ea9d87629" 26 oldMobileUserPasswd = "0x20756cad9b728c8225fd8cedb6badaf8731e174506950219ea657cd54f35f46c" // #nosec G101 27 28 // what we have in table `accounts` before test run: 29 // 1. Address: 0x23A5CEF34B18920785F4B895849936F65CBDEF73 30 // Wallet: 1, Chat: 0, Type: '', Path: m/44'/60'/0'/0/0, Name: 'Main account', Derived_from: '', Pubkey: 0x047B67AD2... 31 // 2. Address: 0x4851276E2B7DC3B8BEF1749127031BCB3578492D 32 // Wallet: 0, Chat: 1, Type: '', Path: m/43'/60'/1581'/0'/0, Name: 'Cadetblue Fuzzy Flickertailsquirrel', Derived_from: '', Pubkey: 0x04F96F6F5... 33 // 3. Address: 0x4D26E5C2F85BA5D10BDA6B031E1C1579F8ECFA1F 34 // Wallet: 0, Chat: 0, Type: 'generated', Path: m/44'/60'/0'/1, Name: 'generated', Derived_from: '', Pubkey: 0x04488EDA7... 35 // 4. Address: 0x516312D69737C5E6EF16F22E0097FF5D9F0C4196 36 // Wallet: 0, Chat: 0, Type: 'key', Path: m/44'/60'/0'/0/0, Name: 'key', Derived_from: '', Pubkey: 0x040D5E4E3... 37 // 5. Address: 0x95222290DD7278AA3DDD389CC1E1D165CC4BAFE5 38 // Wallet: 0, Chat: 0, Type: 'watch', Path: '', Name: 'watch-only', Derived_from: '', Pubkey: <null> 39 // 6. Address: 0xB7A1233D1309CE665A3A4DB088E4A046EB333545 40 // Wallet: 0, Chat: 0, Type: 'seed', Path: m/44'/60'/0'/0/0, Name: 'seed', Derived_from: '', Pubkey: 0x04FDE3E5... 41 // seed phrase for 0xB7A1233D1309CE665A3A4DB088E4A046EB333545: vocal blouse script census island armor seek catch wool narrow peasant attract 42 // private key for 0x516312D69737C5E6EF16F22E0097FF5D9F0C4196: c3ad0b50652318f845565c13761e5369ce75dcbc2a94616e15b829d4b07410fe 43 // status account seed phrase: coin globe kit hamster notable proof orphan always mistake usual morning usage 44 srcFolder = "../static/test-mobile-release-1.20.x-aa6e4b2-account/" 45 ) 46 47 type OldMobileUserUpgradingFromV1ToV2Test struct { 48 suite.Suite 49 tmpdir string 50 } 51 52 type PostLoginCheckCallback func(b *GethStatusBackend) 53 54 func (s *OldMobileUserUpgradingFromV1ToV2Test) SetupTest() { 55 utils.Init() 56 s.tmpdir = s.T().TempDir() 57 copyDir(srcFolder, s.tmpdir, s.T()) 58 } 59 60 func TestOldMobileUserUpgradingFromV1ToV2(t *testing.T) { 61 suite.Run(t, new(OldMobileUserUpgradingFromV1ToV2Test)) 62 } 63 64 func (s *OldMobileUserUpgradingFromV1ToV2Test) loginMobileUser(check PostLoginCheckCallback) { 65 b := NewGethStatusBackend() 66 b.UpdateRootDataDir(s.tmpdir) 67 s.Require().NoError(b.OpenAccounts()) 68 s.Require().NoError(b.Login(oldMobileUserKeyUID, oldMobileUserPasswd)) 69 70 check(b) 71 72 s.Require().NoError(b.Logout()) 73 } 74 75 func (s *OldMobileUserUpgradingFromV1ToV2Test) TestOptimizeMobileWakuV2SettingsForMobileV1() { 76 bkFunc := d_common.IsMobilePlatform 77 d_common.IsMobilePlatform = func() bool { 78 return true 79 } 80 defer func() { 81 d_common.IsMobilePlatform = bkFunc 82 }() 83 84 s.loginMobileUser(func(b *GethStatusBackend) { 85 nc, err := b.GetNodeConfig() 86 s.Require().NoError(err) 87 s.Require().True(nc.WakuV2Config.LightClient) 88 s.Require().False(nc.WakuV2Config.EnableStoreConfirmationForMessagesSent) 89 }) 90 } 91 92 func (s *OldMobileUserUpgradingFromV1ToV2Test) TestLoginAndMigrationsStillWorkWithExistingMobileUser() { 93 checkAfterLogin := func(b *GethStatusBackend) { 94 db, err := accounts.NewDB(b.appDB) 95 s.Require().NoError(err) 96 accs, err := db.GetAllAccounts() 97 s.Require().NoError(err) 98 s.Require().True(len(accs) == 6) 99 kps, err := db.GetAllKeypairs() 100 s.Require().NoError(err) 101 s.Require().True(len(kps) == 3) 102 103 // Create a map to categorize keypairs by their type 104 keypairMap := make(map[accounts.KeypairType][]*accounts.Keypair) 105 for _, kp := range kps { 106 keypairMap[kp.Type] = append(keypairMap[kp.Type], kp) 107 } 108 109 // Check profile keypair 110 profileKps, ok := keypairMap[accounts.KeypairTypeProfile] 111 s.Require().True(ok, "Profile keypair not found") 112 s.Require().True(len(profileKps) == 1, "Unexpected number of profile keypairs") 113 s.Require().True(len(profileKps[0].Accounts) == 3) 114 for _, a := range profileKps[0].Accounts { 115 s.Require().Equal(a.KeyUID, oldMobileUserKeyUID) 116 } 117 118 generator := b.AccountManager().AccountsGenerator() 119 // Check seed keypair 120 seedKps, ok := keypairMap[accounts.KeypairTypeSeed] 121 s.Require().True(ok, "Seed keypair not found") 122 s.Require().True(len(seedKps) == 1, "Unexpected number of seed keypairs") 123 s.Require().True(len(seedKps[0].Accounts) == 1) 124 info, err := generator.LoadAccount(seedKps[0].Accounts[0].Address.Hex(), oldMobileUserPasswd) 125 s.Require().NoError(err) 126 s.Require().Equal(seedKps[0].KeyUID, info.KeyUID) 127 s.Require().Equal(seedKps[0].Accounts[0].KeyUID, info.KeyUID) 128 mnemonicNoExtraSpaces := strings.Join(strings.Fields("vocal blouse script census island armor seek catch wool narrow peasant attract"), " ") 129 importedSeedAccountInfo, err := generator.ImportMnemonic(mnemonicNoExtraSpaces, "") 130 s.Require().NoError(err) 131 derivedAddresses, err := generator.DeriveAddresses(importedSeedAccountInfo.ID, paths) 132 s.Require().NoError(err) 133 s.Require().Equal(derivedAddresses[pathDefaultWallet].PublicKey, "0x04fde3e58a7379161da2adf033fbee076e2ba11fca8b07c4d06610b399911a60017e4c108eae243487d19e273f99c2d6af13ff5e330783f4389212092b01cc616c") 134 //following line shows: we're unable to calculate the right KeyUID with the wrong public key from existing records for the imported seed account 135 s.Require().False(importedSeedAccountInfo.KeyUID == seedKps[0].KeyUID) 136 137 // Check key keypair 138 keyKps, ok := keypairMap[accounts.KeypairTypeKey] 139 s.Require().True(ok, "Key keypair not found") 140 s.Require().True(len(keyKps) == 1, "Unexpected number of key keypairs") 141 s.Require().True(len(keyKps[0].Accounts) == 1) 142 info, err = generator.LoadAccount(keyKps[0].Accounts[0].Address.Hex(), oldMobileUserPasswd) 143 s.Require().NoError(err) 144 s.Require().Equal(keyKps[0].KeyUID, info.KeyUID) 145 s.Require().Equal(keyKps[0].Accounts[0].KeyUID, info.KeyUID) 146 info, err = generator.ImportPrivateKey("c3ad0b50652318f845565c13761e5369ce75dcbc2a94616e15b829d4b07410fe") 147 s.Require().NoError(err) 148 s.Require().Equal(info.KeyUID, keyKps[0].KeyUID) 149 } 150 151 s.loginMobileUser(checkAfterLogin) 152 s.loginMobileUser(checkAfterLogin) // Login twice to catch weird errors that only appear after logout 153 } 154 155 // TestAddWalletAccount we should be able to add a wallet account after upgrading from mobile v1 156 func (s *OldMobileUserUpgradingFromV1ToV2Test) TestAddWalletAccountAfterUpgradingFromMobileV1() { 157 b := NewGethStatusBackend() 158 b.UpdateRootDataDir(s.tmpdir) 159 s.Require().NoError(b.OpenAccounts()) 160 s.Require().NoError(b.Login(oldMobileUserKeyUID, oldMobileUserPasswd)) 161 db, _ := accounts.NewDB(b.appDB) 162 walletRootAddress, err := db.GetWalletRootAddress() 163 s.Require().NoError(err) 164 masterAddress, err := db.GetMasterAddress() 165 s.Require().NoError(err) 166 167 kps, _ := db.GetAllKeypairs() 168 // Create a map to categorize keypairs by their type 169 keypairMap := make(map[accounts.KeypairType][]*accounts.Keypair) 170 for _, kp := range kps { 171 keypairMap[kp.Type] = append(keypairMap[kp.Type], kp) 172 } 173 profileKps := keypairMap[accounts.KeypairTypeProfile] 174 profileKp := profileKps[0] 175 s.Require().True(profileKp.DerivedFrom == walletRootAddress.Hex()) 176 s.Require().False(masterAddress.Hex() == walletRootAddress.Hex()) 177 s.T().Logf("masterAddress: %s, walletRootAddress: %s", masterAddress.Hex(), walletRootAddress.Hex()) 178 179 // simulate mobile frontend adding a wallet account 180 suggestedPath, err := db.ResolveSuggestedPathForKeypair(oldMobileUserKeyUID) 181 s.Require().NoError(err) 182 generator := b.AccountManager().AccountsGenerator() 183 accountInfo, err := generator.LoadAccount(profileKp.DerivedFrom, oldMobileUserPasswd) 184 s.Require().NoError(err) 185 infoMap, err := generator.DeriveAddresses(accountInfo.ID, []string{suggestedPath}) 186 s.Require().NoError(err) 187 s.Require().Len(infoMap, 1) 188 deriveAccountInfo := infoMap[suggestedPath] 189 expectedDerivedAddress := "0xf44F8Ebc5b088e0eA8a0f7309A4a0c525AD783DA" 190 s.Require().Equal(expectedDerivedAddress, deriveAccountInfo.Address) 191 derivedAddress := types.HexToAddress(deriveAccountInfo.Address) 192 accountsAPI := b.StatusNode().AccountService().AccountsAPI() 193 err = accountsAPI.AddAccount(context.Background(), oldMobileUserPasswd, &accounts.Account{ 194 Address: derivedAddress, 195 KeyUID: oldMobileUserKeyUID, 196 Wallet: false, 197 Chat: false, 198 Type: accounts.AccountTypeGenerated, 199 Path: suggestedPath, 200 PublicKey: types.Hex2Bytes(deriveAccountInfo.PublicKey), 201 Name: "GeneratedAccount2", 202 Emoji: "emoji", 203 ColorID: common.CustomizationColorBlue, 204 }) 205 s.Require().NoError(err) 206 // need retry since there's a possible of getting "no key for given address or file" error 207 err = tt.RetryWithBackOff(func() error { 208 return accountsAPI.DeleteAccount(context.Background(), derivedAddress) 209 }) 210 s.Require().NoError(err) 211 s.Require().NoError(b.Logout()) 212 } 213 214 func (s *OldMobileUserUpgradingFromV1ToV2Test) TestFixMissingKeyUIDForAccounts() { 215 db, err := sqlite.OpenDB(sqlite.InMemoryPath, "1234567890", dbsetup.ReducedKDFIterationsNumber) 216 s.Require().NoError(err) 217 tx, err := db.BeginTx(context.Background(), &sql.TxOptions{}) 218 s.Require().NoError(err) 219 s.Require().ErrorContains(appdatabase.FixMissingKeyUIDForAccounts(tx), "no such table: accounts") 220 s.Require().NoError(tx.Rollback()) 221 222 _, err = db.Exec(` 223 create table accounts 224 ( 225 address VARCHAR not null primary key, 226 wallet BOOLEAN, 227 chat BOOLEAN, 228 type TEXT, 229 storage TEXT, 230 pubkey BLOB, 231 path TEXT, 232 name TEXT, 233 color TEXT, 234 created_at DATETIME not null, 235 updated_at DATETIME not null, 236 hidden BOOL default FALSE not null, 237 emoji TEXT default "" not null, 238 derived_from TEXT default "" not null, 239 clock INT default 0 not null 240 ) without rowid;`) 241 s.Require().NoError(err) 242 tx, err = db.BeginTx(context.Background(), &sql.TxOptions{}) 243 s.Require().NoError(err) 244 s.Require().ErrorContains(appdatabase.FixMissingKeyUIDForAccounts(tx), "no such table: settings") 245 s.Require().NoError(tx.Rollback()) 246 247 _, err = db.Exec(` 248 create table settings 249 ( 250 address VARCHAR not null, 251 key_uid VARCHAR not null, 252 latest_derived_path UNSIGNED INT default 0, 253 public_key VARCHAR not null, 254 synthetic_id VARCHAR default 'id' not null primary key, 255 wallet_root_address VARCHAR not null 256 ) without rowid;`) 257 s.Require().NoError(err) 258 tx, err = db.BeginTx(context.Background(), &sql.TxOptions{}) 259 s.Require().NoError(err) 260 // no rows in `settings` table, but we expect no error 261 s.Require().NoError(appdatabase.FixMissingKeyUIDForAccounts(tx)) 262 s.Require().NoError(tx.Commit()) 263 }