github.com/status-im/status-go@v1.1.0/protocol/messenger_sync_wallets_test.go (about) 1 package protocol 2 3 import ( 4 "context" 5 "errors" 6 "testing" 7 8 "github.com/status-im/status-go/eth-node/types" 9 "github.com/status-im/status-go/multiaccounts/accounts" 10 "github.com/status-im/status-go/protocol/encryption/multidevice" 11 "github.com/status-im/status-go/protocol/tt" 12 13 "github.com/stretchr/testify/suite" 14 ) 15 16 func TestMessengerSyncWalletSuite(t *testing.T) { 17 suite.Run(t, new(MessengerSyncWalletSuite)) 18 } 19 20 type MessengerSyncWalletSuite struct { 21 MessengerBaseTestSuite 22 } 23 24 // user should not be able to change a keypair name directly, it follows display name 25 func (s *MessengerSyncWalletSuite) TestProfileKeypairNameChange() { 26 profileKp := accounts.GetProfileKeypairForTest(true, false, false) 27 profileKp.KeyUID = s.m.account.KeyUID 28 profileKp.Name = s.m.account.Name 29 profileKp.Accounts[0].KeyUID = s.m.account.KeyUID 30 31 // Create a main account on alice 32 err := s.m.settings.SaveOrUpdateKeypair(profileKp) 33 s.Require().NoError(err, "profile keypair alice.settings.SaveOrUpdateKeypair") 34 35 // Check account is present in the db 36 dbProfileKp, err := s.m.settings.GetKeypairByKeyUID(profileKp.KeyUID) 37 s.Require().NoError(err) 38 s.Require().True(accounts.SameKeypairs(profileKp, dbProfileKp)) 39 40 // Try to change profile keypair name using `SaveOrUpdateKeypair` function 41 profileKp1 := accounts.GetProfileKeypairForTest(true, false, false) 42 profileKp1.Name = profileKp1.Name + "updated" 43 profileKp1.KeyUID = s.m.account.KeyUID 44 profileKp1.Accounts[0].KeyUID = s.m.account.KeyUID 45 46 err = s.m.SaveOrUpdateKeypair(profileKp1) 47 s.Require().Error(err) 48 s.Require().True(err == ErrCannotChangeKeypairName) 49 50 // Check the db 51 dbProfileKp, err = s.m.settings.GetKeypairByKeyUID(profileKp.KeyUID) 52 s.Require().NoError(err) 53 s.Require().True(accounts.SameKeypairs(profileKp, dbProfileKp)) 54 55 // Try to change profile keypair name using `UpdateKeypairName` function 56 err = s.m.UpdateKeypairName(profileKp1.KeyUID, profileKp1.Name) 57 s.Require().Error(err) 58 s.Require().True(err == ErrCannotChangeKeypairName) 59 60 // Check the db 61 dbProfileKp, err = s.m.settings.GetKeypairByKeyUID(profileKp.KeyUID) 62 s.Require().NoError(err) 63 s.Require().True(accounts.SameKeypairs(profileKp, dbProfileKp)) 64 } 65 66 func (s *MessengerSyncWalletSuite) TestSyncWallets() { 67 profileKp := accounts.GetProfileKeypairForTest(true, true, true) 68 // set clocks for accounts 69 profileKp.Clock = uint64(len(profileKp.Accounts) - 1) 70 for i, acc := range profileKp.Accounts { 71 acc.Clock = uint64(i) 72 } 73 74 // Create a main account on alice 75 err := s.m.settings.SaveOrUpdateKeypair(profileKp) 76 s.Require().NoError(err, "profile keypair alice.settings.SaveOrUpdateKeypair") 77 78 // Check account is present in the db 79 dbProfileKp1, err := s.m.settings.GetKeypairByKeyUID(profileKp.KeyUID) 80 s.Require().NoError(err) 81 s.Require().True(accounts.SameKeypairs(profileKp, dbProfileKp1)) 82 83 // Create new device and add main account to 84 alicesOtherDevice, err := newMessengerWithKey(s.shh, s.m.identity, s.logger, nil) 85 s.Require().NoError(err) 86 87 // Store only chat and default wallet account on other device 88 profileKpOtherDevice := accounts.GetProfileKeypairForTest(true, true, false) 89 err = alicesOtherDevice.settings.SaveOrUpdateKeypair(profileKpOtherDevice) 90 s.Require().NoError(err, "profile keypair alicesOtherDevice.settings.SaveOrUpdateKeypair") 91 92 // Check account is present in the db 93 dbProfileKp2, err := alicesOtherDevice.settings.GetKeypairByKeyUID(profileKpOtherDevice.KeyUID) 94 s.Require().NoError(err) 95 s.Require().True(accounts.SameKeypairs(profileKpOtherDevice, dbProfileKp2)) 96 97 // Pair devices 98 im1 := &multidevice.InstallationMetadata{ 99 Name: "alice's-other-device", 100 DeviceType: "alice's-other-device-type", 101 } 102 err = alicesOtherDevice.SetInstallationMetadata(alicesOtherDevice.installationID, im1) 103 s.Require().NoError(err) 104 response, err := alicesOtherDevice.SendPairInstallation(context.Background(), nil) 105 s.Require().NoError(err) 106 s.Require().NotNil(response) 107 s.Require().Len(response.Chats(), 1) 108 s.Require().False(response.Chats()[0].Active) 109 110 // Wait for the message to reach its destination 111 response, err = WaitOnMessengerResponse( 112 s.m, 113 func(r *MessengerResponse) bool { return len(r.Installations()) > 0 }, 114 "installation not received", 115 ) 116 117 s.Require().NoError(err) 118 actualInstallation := response.Installations()[0] 119 s.Require().Equal(alicesOtherDevice.installationID, actualInstallation.ID) 120 s.Require().NotNil(actualInstallation.InstallationMetadata) 121 s.Require().Equal("alice's-other-device", actualInstallation.InstallationMetadata.Name) 122 s.Require().Equal("alice's-other-device-type", actualInstallation.InstallationMetadata.DeviceType) 123 124 err = s.m.EnableInstallation(alicesOtherDevice.installationID) 125 s.Require().NoError(err) 126 127 // Store seed phrase keypair with accounts on alice's device 128 seedPhraseKp := accounts.GetSeedImportedKeypair1ForTest() 129 err = s.m.settings.SaveOrUpdateKeypair(seedPhraseKp) 130 s.Require().NoError(err, "seed phrase keypair alice.settings.SaveOrUpdateKeypair") 131 132 dbSeedPhraseKp1, err := s.m.settings.GetKeypairByKeyUID(seedPhraseKp.KeyUID) 133 s.Require().NoError(err) 134 s.Require().True(accounts.SameKeypairs(seedPhraseKp, dbSeedPhraseKp1)) 135 136 // Store private key keypair with accounts on alice's device 137 privKeyKp := accounts.GetPrivKeyImportedKeypairForTest() 138 err = s.m.settings.SaveOrUpdateKeypair(privKeyKp) 139 s.Require().NoError(err, "private key keypair alice.settings.SaveOrUpdateKeypair") 140 141 dbPrivKeyKp1, err := s.m.settings.GetKeypairByKeyUID(privKeyKp.KeyUID) 142 s.Require().NoError(err) 143 s.Require().True(accounts.SameKeypairs(privKeyKp, dbPrivKeyKp1)) 144 145 // Store watch only accounts on alice's device 146 woAccounts := accounts.GetWatchOnlyAccountsForTest() 147 err = s.m.settings.SaveOrUpdateAccounts(woAccounts, false) 148 s.Require().NoError(err) 149 dbWoAccounts1, err := s.m.settings.GetActiveWatchOnlyAccounts() 150 s.Require().NoError(err) 151 s.Require().Equal(len(woAccounts), len(dbWoAccounts1)) 152 s.Require().True(haveSameElements(woAccounts, dbWoAccounts1, accounts.SameAccounts)) 153 154 dbAccounts1, err := s.m.settings.GetActiveAccounts() 155 s.Require().NoError(err) 156 s.Require().Equal(len(profileKp.Accounts)+len(seedPhraseKp.Accounts)+len(privKeyKp.Accounts)+len(woAccounts), len(dbAccounts1)) 157 158 // Trigger's a sync between devices 159 err = s.m.SyncDevices(context.Background(), "ens-name", "profile-image", nil) 160 s.Require().NoError(err) 161 162 err = tt.RetryWithBackOff(func() error { 163 response, err := alicesOtherDevice.RetrieveAll() 164 if err != nil { 165 return err 166 } 167 168 if len(response.Keypairs) != 3 || // 3 keypairs (profile, seed, priv key) 169 len(response.WatchOnlyAccounts) != len(woAccounts) { 170 return errors.New("no sync wallet account received") 171 } 172 return nil 173 }) 174 s.Require().NoError(err) 175 176 dbProfileKp2, err = alicesOtherDevice.settings.GetKeypairByKeyUID(profileKp.KeyUID) 177 s.Require().NoError(err) 178 s.Require().True(profileKp.KeyUID == dbProfileKp2.KeyUID && 179 profileKp.Name == dbProfileKp2.Name && 180 profileKp.Type == dbProfileKp2.Type && 181 profileKp.DerivedFrom == dbProfileKp2.DerivedFrom && 182 profileKp.LastUsedDerivationIndex == dbProfileKp2.LastUsedDerivationIndex && 183 profileKp.Clock == dbProfileKp2.Clock && 184 len(profileKp.Accounts) == len(dbProfileKp2.Accounts)) 185 // chat and default wallet account should be fully operable, other accounts partially operable 186 for i := range profileKp.Accounts { 187 match := false 188 expectedOperableValue := accounts.AccountPartiallyOperable 189 if profileKp.Accounts[i].Chat || profileKp.Accounts[i].Wallet { 190 expectedOperableValue = accounts.AccountFullyOperable 191 } 192 for j := range dbProfileKp2.Accounts { 193 if accounts.SameAccountsWithDifferentOperable(profileKp.Accounts[i], dbProfileKp2.Accounts[j], expectedOperableValue) { 194 match = true 195 break 196 } 197 } 198 s.Require().True(match) 199 } 200 201 dbSeedPhraseKp2, err := alicesOtherDevice.settings.GetKeypairByKeyUID(seedPhraseKp.KeyUID) 202 s.Require().NoError(err) 203 s.Require().True(accounts.SameKeypairsWithDifferentSyncedFrom(seedPhraseKp, dbSeedPhraseKp2, true, "", accounts.AccountNonOperable)) 204 205 dbPrivKeyKp2, err := alicesOtherDevice.settings.GetKeypairByKeyUID(privKeyKp.KeyUID) 206 s.Require().NoError(err) 207 s.Require().True(accounts.SameKeypairsWithDifferentSyncedFrom(privKeyKp, dbPrivKeyKp2, true, "", accounts.AccountNonOperable)) 208 209 dbWoAccounts2, err := alicesOtherDevice.settings.GetActiveWatchOnlyAccounts() 210 s.Require().NoError(err) 211 s.Require().Equal(len(woAccounts), len(dbWoAccounts2)) 212 s.Require().True(haveSameElements(woAccounts, dbWoAccounts2, accounts.SameAccounts)) 213 214 dbAccounts2, err := alicesOtherDevice.settings.GetActiveAccounts() 215 s.Require().NoError(err) 216 s.Require().Equal(len(profileKp.Accounts)+len(seedPhraseKp.Accounts)+len(privKeyKp.Accounts)+len(woAccounts), len(dbAccounts2)) 217 218 s.Require().True(haveSameElements(dbAccounts1, dbAccounts2, accounts.SameAccounts)) 219 220 // Update keypair name on alice's primary device 221 profileKpUpdated := accounts.GetProfileKeypairForTest(true, true, false) 222 profileKpUpdated.Name = profileKp.Name + "Updated" 223 profileKpUpdated.Accounts = profileKp.Accounts[:0] 224 err = s.m.SaveOrUpdateKeypair(profileKpUpdated) 225 s.Require().NoError(err, "updated keypair name on alice primary device") 226 227 // Sync between devices is triggered automatically 228 // via watch account changes subscription 229 // Retrieve community link & community 230 err = tt.RetryWithBackOff(func() error { 231 response, err := alicesOtherDevice.RetrieveAll() 232 if err != nil { 233 return err 234 } 235 236 if len(response.Keypairs) != 1 { 237 return errors.New("no sync keypairs received") 238 } 239 return nil 240 }) 241 s.Require().NoError(err) 242 243 // check on alice's other device 244 dbProfileKp2, err = alicesOtherDevice.settings.GetKeypairByKeyUID(profileKp.KeyUID) 245 s.Require().NoError(err) 246 s.Require().Equal(profileKpUpdated.Name, dbProfileKp2.Name) 247 248 // Update accounts on alice's primary device 249 profileKpUpdated = accounts.GetProfileKeypairForTest(true, true, true) 250 accountsToUpdate := profileKpUpdated.Accounts[2:] 251 for _, acc := range accountsToUpdate { 252 acc.Name = acc.Name + "Updated" 253 acc.ColorID = acc.ColorID + "Updated" 254 acc.Emoji = acc.Emoji + "Updated" 255 err = s.m.SaveOrUpdateAccount(acc) 256 s.Require().NoError(err, "updated account on alice primary device") 257 } 258 259 err = tt.RetryWithBackOff(func() error { 260 response, err := alicesOtherDevice.RetrieveAll() 261 if err != nil { 262 return err 263 } 264 265 if len(response.Keypairs) != 2 { 266 return errors.New("no sync keypairs received") 267 } 268 return nil 269 }) 270 s.Require().NoError(err) 271 272 // check on alice's other device 273 dbProfileKp2, err = alicesOtherDevice.settings.GetKeypairByKeyUID(profileKp.KeyUID) 274 s.Require().NoError(err) 275 for _, acc := range accountsToUpdate { 276 s.Require().True(contains(dbProfileKp2.Accounts, acc, accounts.SameAccounts)) 277 } 278 } 279 280 func (s *MessengerSyncWalletSuite) TestSyncWalletAccountsReorder() { 281 profileKp := accounts.GetProfileKeypairForTest(true, false, false) 282 profileKp.Accounts[0].Position = -1 // Chat account must be at position -1 always 283 284 woAccounts := []*accounts.Account{ 285 {Address: types.Address{0x11}, Type: accounts.AccountTypeWatch, Position: 0}, 286 {Address: types.Address{0x12}, Type: accounts.AccountTypeWatch, Position: 1}, 287 {Address: types.Address{0x13}, Type: accounts.AccountTypeWatch, Position: 2}, 288 {Address: types.Address{0x14}, Type: accounts.AccountTypeWatch, Position: 3}, 289 {Address: types.Address{0x15}, Type: accounts.AccountTypeWatch, Position: 4}, 290 {Address: types.Address{0x16}, Type: accounts.AccountTypeWatch, Position: 5}, 291 } 292 293 // Create a main account on alice 294 err := s.m.settings.SaveOrUpdateKeypair(profileKp) 295 s.Require().NoError(err, "profile keypair alice.settings.SaveOrUpdateKeypair") 296 // Store watch only accounts on alice's device 297 err = s.m.settings.SaveOrUpdateAccounts(woAccounts, false) 298 s.Require().NoError(err, "wo accounts alice.settings.SaveOrUpdateKeypair") 299 300 dbAccounts, err := s.m.settings.GetActiveAccounts() 301 s.Require().NoError(err) 302 s.Require().Equal(len(woAccounts), len(dbAccounts)-1) 303 304 // Create a main account on alice's other device 305 alicesOtherDevice, err := newMessengerWithKey(s.shh, s.m.identity, s.logger, nil) 306 s.Require().NoError(err) 307 err = alicesOtherDevice.settings.SaveOrUpdateKeypair(profileKp) 308 s.Require().NoError(err, "profile keypair alice.settings.SaveOrUpdateKeypair") 309 // Store watch only accounts on alice's other device 310 err = alicesOtherDevice.settings.SaveOrUpdateAccounts(woAccounts, false) 311 s.Require().NoError(err, "wo accounts alice.settings.SaveOrUpdateKeypair") 312 313 dbAccounts, err = alicesOtherDevice.settings.GetActiveAccounts() 314 s.Require().NoError(err) 315 s.Require().Equal(len(woAccounts), len(dbAccounts)-1) 316 317 // Pair devices 318 im1 := &multidevice.InstallationMetadata{ 319 Name: "alice's-other-device", 320 DeviceType: "alice's-other-device-type", 321 } 322 err = alicesOtherDevice.SetInstallationMetadata(alicesOtherDevice.installationID, im1) 323 s.Require().NoError(err) 324 response, err := alicesOtherDevice.SendPairInstallation(context.Background(), nil) 325 s.Require().NoError(err) 326 s.Require().NotNil(response) 327 s.Require().Len(response.Chats(), 1) 328 s.Require().False(response.Chats()[0].Active) 329 330 // Wait for the message to reach its destination 331 response, err = WaitOnMessengerResponse( 332 s.m, 333 func(r *MessengerResponse) bool { return len(r.Installations()) > 0 }, 334 "installation not received", 335 ) 336 337 s.Require().NoError(err) 338 actualInstallation := response.Installations()[0] 339 s.Require().Equal(alicesOtherDevice.installationID, actualInstallation.ID) 340 s.Require().NotNil(actualInstallation.InstallationMetadata) 341 s.Require().Equal("alice's-other-device", actualInstallation.InstallationMetadata.Name) 342 s.Require().Equal("alice's-other-device-type", actualInstallation.InstallationMetadata.DeviceType) 343 344 err = s.m.EnableInstallation(alicesOtherDevice.installationID) 345 s.Require().NoError(err) 346 347 // Move down account from position 1 to position 4 348 err = s.m.MoveWalletAccount(1, 4) 349 s.Require().NoError(err) 350 351 // Expected after moving down 352 woAccounts = []*accounts.Account{ 353 {Address: types.Address{0x11}, Type: accounts.AccountTypeWatch, Position: 0}, 354 {Address: types.Address{0x13}, Type: accounts.AccountTypeWatch, Position: 1}, 355 {Address: types.Address{0x14}, Type: accounts.AccountTypeWatch, Position: 2}, 356 {Address: types.Address{0x15}, Type: accounts.AccountTypeWatch, Position: 3}, 357 {Address: types.Address{0x12}, Type: accounts.AccountTypeWatch, Position: 4}, // acc with addr 0x12 is at position 4 (moved from position 1) 358 {Address: types.Address{0x16}, Type: accounts.AccountTypeWatch, Position: 5}, 359 } 360 361 dbAccounts, err = s.m.settings.GetActiveAccounts() 362 s.Require().NoError(err) 363 s.Require().Equal(len(woAccounts), len(dbAccounts)-1) 364 for i := 0; i < len(woAccounts); i++ { 365 s.Require().True(accounts.SameAccountsIncludingPosition(woAccounts[i], dbAccounts[i+1])) 366 } 367 368 // Sync between devices is triggered automatically 369 err = tt.RetryWithBackOff(func() error { 370 response, err := alicesOtherDevice.RetrieveAll() 371 if err != nil { 372 return err 373 } 374 375 if len(response.AccountsPositions) != len(woAccounts) { 376 return errors.New("no sync message received for accounts reordering") 377 } 378 return nil 379 }) 380 s.Require().NoError(err) 381 382 // check on alice's other device 383 dbAccounts, err = alicesOtherDevice.settings.GetActiveAccounts() 384 s.Require().NoError(err) 385 s.Require().Equal(len(woAccounts), len(dbAccounts)-1) 386 for i := 0; i < len(woAccounts); i++ { 387 s.Require().True(accounts.SameAccountsIncludingPosition(woAccounts[i], dbAccounts[i+1])) 388 } 389 390 // compare times 391 dbClock, err := s.m.settings.GetClockOfLastAccountsPositionChange() 392 s.Require().NoError(err) 393 dbClockOtherDevice, err := s.m.settings.GetClockOfLastAccountsPositionChange() 394 s.Require().NoError(err) 395 s.Require().Equal(dbClock, dbClockOtherDevice) 396 397 // Move up account from position 5 to position 0 398 err = s.m.MoveWalletAccount(5, 0) 399 s.Require().NoError(err) 400 401 // Expected after moving down 402 woAccounts = []*accounts.Account{ 403 {Address: types.Address{0x16}, Type: accounts.AccountTypeWatch, Position: 0}, // acc with addr 0x16 is at position 0 (moved from position 5) 404 {Address: types.Address{0x11}, Type: accounts.AccountTypeWatch, Position: 1}, 405 {Address: types.Address{0x13}, Type: accounts.AccountTypeWatch, Position: 2}, 406 {Address: types.Address{0x14}, Type: accounts.AccountTypeWatch, Position: 3}, 407 {Address: types.Address{0x15}, Type: accounts.AccountTypeWatch, Position: 4}, 408 {Address: types.Address{0x12}, Type: accounts.AccountTypeWatch, Position: 5}, 409 } 410 411 dbAccounts, err = s.m.settings.GetActiveAccounts() 412 s.Require().NoError(err) 413 s.Require().Equal(len(woAccounts), len(dbAccounts)-1) 414 for i := 0; i < len(woAccounts); i++ { 415 s.Require().True(accounts.SameAccountsIncludingPosition(woAccounts[i], dbAccounts[i+1])) 416 } 417 418 // Sync between devices is triggered automatically 419 err = tt.RetryWithBackOff(func() error { 420 response, err := alicesOtherDevice.RetrieveAll() 421 if err != nil { 422 return err 423 } 424 425 if len(response.AccountsPositions) != len(woAccounts) { 426 return errors.New("no sync message received for accounts reordering") 427 } 428 return nil 429 }) 430 s.Require().NoError(err) 431 432 // check on alice's other device 433 dbAccounts, err = alicesOtherDevice.settings.GetActiveAccounts() 434 s.Require().NoError(err) 435 s.Require().Equal(len(woAccounts), len(dbAccounts)-1) 436 for i := 0; i < len(woAccounts); i++ { 437 s.Require().True(accounts.SameAccountsIncludingPosition(woAccounts[i], dbAccounts[i+1])) 438 } 439 440 // compare times 441 dbClock, err = s.m.settings.GetClockOfLastAccountsPositionChange() 442 s.Require().NoError(err) 443 dbClockOtherDevice, err = s.m.settings.GetClockOfLastAccountsPositionChange() 444 s.Require().NoError(err) 445 s.Require().Equal(dbClock, dbClockOtherDevice) 446 } 447 448 func (s *MessengerSyncWalletSuite) TestSyncWalletAccountOrderAfterDeletion() { 449 profileKp := accounts.GetProfileKeypairForTest(true, true, true) 450 // set clocks for accounts 451 profileKp.Clock = uint64(len(profileKp.Accounts) - 1) 452 i := -1 453 for _, acc := range profileKp.Accounts { 454 acc.Clock = uint64(i + 1) 455 acc.Position = int64(i) 456 acc.Operable = accounts.AccountNonOperable 457 i++ 458 } 459 460 // Create a main account on alice 461 err := s.m.settings.SaveOrUpdateKeypair(profileKp) 462 s.Require().NoError(err, "profile keypair alice.settings.SaveOrUpdateKeypair") 463 // Store seed phrase keypair with accounts on alice's device 464 seedPhraseKp := accounts.GetSeedImportedKeypair1ForTest() 465 for _, acc := range seedPhraseKp.Accounts { 466 acc.Clock = uint64(i + 1) 467 acc.Position = int64(i) 468 acc.Operable = accounts.AccountNonOperable 469 i++ 470 } 471 err = s.m.settings.SaveOrUpdateKeypair(seedPhraseKp) 472 s.Require().NoError(err, "seed phrase keypair alice.settings.SaveOrUpdateKeypair") 473 // Store private key keypair with accounts on alice's device 474 privKeyKp := accounts.GetPrivKeyImportedKeypairForTest() 475 for _, acc := range privKeyKp.Accounts { 476 acc.Clock = uint64(i + 1) 477 acc.Position = int64(i) 478 acc.Operable = accounts.AccountNonOperable 479 i++ 480 } 481 err = s.m.settings.SaveOrUpdateKeypair(privKeyKp) 482 s.Require().NoError(err, "private key keypair alice.settings.SaveOrUpdateKeypair") 483 // Store watch only accounts on alice's device 484 woAccounts := accounts.GetWatchOnlyAccountsForTest() 485 for _, acc := range woAccounts { 486 acc.Clock = uint64(i + 1) 487 acc.Position = int64(i) 488 acc.Operable = accounts.AccountFullyOperable 489 i++ 490 } 491 err = s.m.settings.SaveOrUpdateAccounts(woAccounts, false) 492 s.Require().NoError(err) 493 // Check accounts 494 dbAccounts1, err := s.m.settings.GetActiveAccounts() 495 s.Require().NoError(err) 496 totalNumOfAccounts := len(profileKp.Accounts) + len(seedPhraseKp.Accounts) + len(privKeyKp.Accounts) + len(woAccounts) 497 s.Require().Equal(totalNumOfAccounts, len(dbAccounts1)) 498 499 // Create new device and add main account to 500 alicesOtherDevice, err := newMessengerWithKey(s.shh, s.m.identity, s.logger, nil) 501 s.Require().NoError(err) 502 // Store only chat and default wallet account on other device 503 profileKpOtherDevice := accounts.GetProfileKeypairForTest(true, true, false) 504 err = alicesOtherDevice.settings.SaveOrUpdateKeypair(profileKpOtherDevice) 505 s.Require().NoError(err, "profile keypair alicesOtherDevice.settings.SaveOrUpdateKeypair") 506 507 // Pair devices 508 im1 := &multidevice.InstallationMetadata{ 509 Name: "alice's-other-device", 510 DeviceType: "alice's-other-device-type", 511 } 512 err = alicesOtherDevice.SetInstallationMetadata(alicesOtherDevice.installationID, im1) 513 s.Require().NoError(err) 514 response, err := alicesOtherDevice.SendPairInstallation(context.Background(), nil) 515 s.Require().NoError(err) 516 s.Require().NotNil(response) 517 s.Require().Len(response.Chats(), 1) 518 s.Require().False(response.Chats()[0].Active) 519 520 // Wait for the message to reach its destination 521 response, err = WaitOnMessengerResponse( 522 s.m, 523 func(r *MessengerResponse) bool { return len(r.Installations()) > 0 }, 524 "installation not received", 525 ) 526 527 s.Require().NoError(err) 528 actualInstallation := response.Installations()[0] 529 s.Require().Equal(alicesOtherDevice.installationID, actualInstallation.ID) 530 s.Require().NotNil(actualInstallation.InstallationMetadata) 531 s.Require().Equal("alice's-other-device", actualInstallation.InstallationMetadata.Name) 532 s.Require().Equal("alice's-other-device-type", actualInstallation.InstallationMetadata.DeviceType) 533 534 err = s.m.EnableInstallation(alicesOtherDevice.installationID) 535 s.Require().NoError(err) 536 537 // Trigger's a sync between devices 538 err = s.m.SyncDevices(context.Background(), "ens-name", "profile-image", nil) 539 s.Require().NoError(err) 540 541 err = tt.RetryWithBackOff(func() error { 542 response, err := alicesOtherDevice.RetrieveAll() 543 if err != nil { 544 return err 545 } 546 547 if len(response.Keypairs) != 3 || // 3 keypairs (profile, seed, priv key) 548 len(response.WatchOnlyAccounts) != len(woAccounts) || 549 len(response.AccountsPositions) != totalNumOfAccounts-1 /* we don't include chat account in position ordering*/ { 550 return errors.New("no sync wallet account received") 551 } 552 return nil 553 }) 554 s.Require().NoError(err) 555 556 dbAccounts2, err := alicesOtherDevice.settings.GetActiveAccounts() 557 s.Require().NoError(err) 558 s.Require().Equal(totalNumOfAccounts, len(dbAccounts2)) 559 560 s.Require().True(haveSameElements(dbAccounts1, dbAccounts2, accounts.SameAccountsIncludingPosition)) 561 562 // Delete keypair related account on alice's primary device 563 accToDelete := seedPhraseKp.Accounts[1] 564 err = s.m.DeleteAccount(accToDelete.Address) 565 s.Require().NoError(err, "delete account on alice primary device") 566 567 totalNumOfAccounts-- //one acc less 568 569 err = tt.RetryWithBackOff(func() error { 570 response, err := alicesOtherDevice.RetrieveAll() 571 if err != nil { 572 return err 573 } 574 575 if len(response.Keypairs) != 1 { 576 return errors.New("no sync keypairs received") 577 } 578 return nil 579 }) 580 s.Require().NoError(err) 581 582 dbAccounts1, err = s.m.settings.GetActiveAccounts() 583 s.Require().NoError(err) 584 s.Require().Equal(totalNumOfAccounts, len(dbAccounts1)) 585 586 dbAccounts2, err = alicesOtherDevice.settings.GetActiveAccounts() 587 s.Require().NoError(err) 588 s.Require().Equal(totalNumOfAccounts, len(dbAccounts2)) 589 590 s.Require().True(haveSameElements(dbAccounts1, dbAccounts2, accounts.SameAccountsIncludingPosition)) 591 592 // Delete watch only account on alice's primary device 593 accToDelete = woAccounts[1] 594 err = s.m.DeleteAccount(accToDelete.Address) 595 s.Require().NoError(err, "delete account on alice primary device") 596 597 totalNumOfAccounts-- //one acc less 598 599 err = tt.RetryWithBackOff(func() error { 600 response, err := alicesOtherDevice.RetrieveAll() 601 if err != nil { 602 return err 603 } 604 605 if len(response.WatchOnlyAccounts) != 1 { 606 return errors.New("no sync keypairs received") 607 } 608 return nil 609 }) 610 s.Require().NoError(err) 611 612 dbAccounts1, err = s.m.settings.GetActiveAccounts() 613 s.Require().NoError(err) 614 s.Require().Equal(totalNumOfAccounts, len(dbAccounts1)) 615 616 dbAccounts2, err = alicesOtherDevice.settings.GetActiveAccounts() 617 s.Require().NoError(err) 618 s.Require().Equal(totalNumOfAccounts, len(dbAccounts2)) 619 620 s.Require().True(haveSameElements(dbAccounts1, dbAccounts2, accounts.SameAccountsIncludingPosition)) 621 }