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  }