github.com/status-im/status-go@v1.1.0/protocol/messenger_backup.go (about)

     1  package protocol
     2  
     3  import (
     4  	"context"
     5  	"time"
     6  
     7  	"github.com/golang/protobuf/proto"
     8  	"go.uber.org/zap"
     9  
    10  	"github.com/status-im/status-go/multiaccounts/accounts"
    11  	multiaccountscommon "github.com/status-im/status-go/multiaccounts/common"
    12  	"github.com/status-im/status-go/multiaccounts/settings"
    13  	"github.com/status-im/status-go/protocol/common"
    14  	"github.com/status-im/status-go/protocol/communities"
    15  	"github.com/status-im/status-go/protocol/protobuf"
    16  )
    17  
    18  const (
    19  	BackupContactsPerBatch = 20
    20  )
    21  
    22  // backupTickerInterval is how often we should check for backups
    23  var backupTickerInterval = 120 * time.Second
    24  
    25  // backupIntervalSeconds is the amount of seconds we should allow between
    26  // backups
    27  var backupIntervalSeconds uint64 = 28800
    28  
    29  type CommunitySet struct {
    30  	Joined  []*communities.Community
    31  	Deleted []*communities.Community
    32  }
    33  
    34  func (m *Messenger) backupEnabled() (bool, error) {
    35  	return m.settings.BackupEnabled()
    36  }
    37  
    38  func (m *Messenger) lastBackup() (uint64, error) {
    39  	return m.settings.LastBackup()
    40  }
    41  
    42  func (m *Messenger) startBackupLoop() {
    43  	ticker := time.NewTicker(backupTickerInterval)
    44  	go func() {
    45  		for {
    46  			select {
    47  			case <-ticker.C:
    48  				if !m.Online() {
    49  					continue
    50  				}
    51  
    52  				enabled, err := m.backupEnabled()
    53  				if err != nil {
    54  					m.logger.Error("failed to fetch backup enabled")
    55  					continue
    56  				}
    57  				if !enabled {
    58  					m.logger.Debug("backup not enabled, skipping")
    59  					continue
    60  				}
    61  
    62  				lastBackup, err := m.lastBackup()
    63  				if err != nil {
    64  					m.logger.Error("failed to fetch last backup time")
    65  					continue
    66  				}
    67  
    68  				now := time.Now().Unix()
    69  				if uint64(now) <= backupIntervalSeconds+lastBackup {
    70  					m.logger.Debug("not backing up")
    71  					continue
    72  				}
    73  				m.logger.Debug("backing up data")
    74  
    75  				ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
    76  				defer cancel()
    77  				_, err = m.BackupData(ctx)
    78  				if err != nil {
    79  					m.logger.Error("failed to backup data", zap.Error(err))
    80  				}
    81  			case <-m.quit:
    82  				ticker.Stop()
    83  				return
    84  			}
    85  		}
    86  	}()
    87  }
    88  
    89  func (m *Messenger) BackupData(ctx context.Context) (uint64, error) {
    90  	clock, chat := m.getLastClockWithRelatedChat()
    91  	contactsToBackup := m.backupContacts(ctx)
    92  	communitiesToBackup, err := m.backupCommunities(ctx, clock)
    93  	if err != nil {
    94  		return 0, err
    95  	}
    96  	chatsToBackup := m.backupChats(ctx, clock)
    97  	if err != nil {
    98  		return 0, err
    99  	}
   100  	profileToBackup, err := m.backupProfile(ctx, clock)
   101  	if err != nil {
   102  		return 0, err
   103  	}
   104  	_, settings, errors := m.prepareSyncSettingsMessages(clock, true)
   105  	if len(errors) != 0 {
   106  		// return just the first error, the others have been logged
   107  		return 0, errors[0]
   108  	}
   109  
   110  	keypairsToBackup, err := m.backupKeypairs()
   111  	if err != nil {
   112  		return 0, err
   113  	}
   114  
   115  	woAccountsToBackup, err := m.backupWatchOnlyAccounts()
   116  	if err != nil {
   117  		return 0, err
   118  	}
   119  
   120  	backupDetailsOnly := func() *protobuf.Backup {
   121  		return &protobuf.Backup{
   122  			Clock: clock,
   123  			ChatsDetails: &protobuf.FetchingBackedUpDataDetails{
   124  				DataNumber:  uint32(0),
   125  				TotalNumber: uint32(len(chatsToBackup)),
   126  			},
   127  			ContactsDetails: &protobuf.FetchingBackedUpDataDetails{
   128  				DataNumber:  uint32(0),
   129  				TotalNumber: uint32(len(contactsToBackup)),
   130  			},
   131  			CommunitiesDetails: &protobuf.FetchingBackedUpDataDetails{
   132  				DataNumber:  uint32(0),
   133  				TotalNumber: uint32(len(communitiesToBackup)),
   134  			},
   135  			ProfileDetails: &protobuf.FetchingBackedUpDataDetails{
   136  				DataNumber:  uint32(0),
   137  				TotalNumber: uint32(len(profileToBackup)),
   138  			},
   139  			SettingsDetails: &protobuf.FetchingBackedUpDataDetails{
   140  				DataNumber:  uint32(0),
   141  				TotalNumber: uint32(len(settings)),
   142  			},
   143  			KeypairDetails: &protobuf.FetchingBackedUpDataDetails{
   144  				DataNumber:  uint32(0),
   145  				TotalNumber: uint32(len(keypairsToBackup)),
   146  			},
   147  			WatchOnlyAccountDetails: &protobuf.FetchingBackedUpDataDetails{
   148  				DataNumber:  uint32(0),
   149  				TotalNumber: uint32(len(woAccountsToBackup)),
   150  			},
   151  		}
   152  	}
   153  
   154  	// Update contacts messages encode and dispatch
   155  	for i, d := range contactsToBackup {
   156  		pb := backupDetailsOnly()
   157  		pb.ContactsDetails.DataNumber = uint32(i + 1)
   158  		pb.Contacts = d.Contacts
   159  		err = m.encodeAndDispatchBackupMessage(ctx, pb, chat.ID)
   160  		if err != nil {
   161  			return 0, err
   162  		}
   163  	}
   164  
   165  	// Update communities messages encode and dispatch
   166  	for i, d := range communitiesToBackup {
   167  		pb := backupDetailsOnly()
   168  		pb.CommunitiesDetails.DataNumber = uint32(i + 1)
   169  		pb.Communities = d.Communities
   170  		err = m.encodeAndDispatchBackupMessage(ctx, pb, chat.ID)
   171  		if err != nil {
   172  			return 0, err
   173  		}
   174  	}
   175  
   176  	// Update profile messages encode and dispatch
   177  	for i, d := range profileToBackup {
   178  		pb := backupDetailsOnly()
   179  		pb.ProfileDetails.DataNumber = uint32(i + 1)
   180  		pb.Profile = d.Profile
   181  		err = m.encodeAndDispatchBackupMessage(ctx, pb, chat.ID)
   182  		if err != nil {
   183  			return 0, err
   184  		}
   185  	}
   186  
   187  	// Update chats encode and dispatch
   188  	for i, d := range chatsToBackup {
   189  		pb := backupDetailsOnly()
   190  		pb.ChatsDetails.DataNumber = uint32(i + 1)
   191  		pb.Chats = d.Chats
   192  		err = m.encodeAndDispatchBackupMessage(ctx, pb, chat.ID)
   193  		if err != nil {
   194  			return 0, err
   195  		}
   196  	}
   197  
   198  	// Update settings messages encode and dispatch
   199  	for i, d := range settings {
   200  		pb := backupDetailsOnly()
   201  		pb.SettingsDetails.DataNumber = uint32(i + 1)
   202  		pb.Setting = d
   203  		err = m.encodeAndDispatchBackupMessage(ctx, pb, chat.ID)
   204  		if err != nil {
   205  			return 0, err
   206  		}
   207  	}
   208  
   209  	// Update keypairs messages encode and dispatch
   210  	for i, d := range keypairsToBackup {
   211  		pb := backupDetailsOnly()
   212  		pb.KeypairDetails.DataNumber = uint32(i + 1)
   213  		pb.Keypair = d.Keypair
   214  		err = m.encodeAndDispatchBackupMessage(ctx, pb, chat.ID)
   215  		if err != nil {
   216  			return 0, err
   217  		}
   218  	}
   219  
   220  	// Update watch only messages encode and dispatch
   221  	for i, d := range woAccountsToBackup {
   222  		pb := backupDetailsOnly()
   223  		pb.WatchOnlyAccountDetails.DataNumber = uint32(i + 1)
   224  		pb.WatchOnlyAccount = d.WatchOnlyAccount
   225  		err = m.encodeAndDispatchBackupMessage(ctx, pb, chat.ID)
   226  		if err != nil {
   227  			return 0, err
   228  		}
   229  	}
   230  
   231  	chat.LastClockValue = clock
   232  	err = m.saveChat(chat)
   233  	if err != nil {
   234  		return 0, err
   235  	}
   236  
   237  	clockInSeconds := clock / 1000
   238  	err = m.settings.SetLastBackup(clockInSeconds)
   239  	if err != nil {
   240  		return 0, err
   241  	}
   242  	if m.config.messengerSignalsHandler != nil {
   243  		m.config.messengerSignalsHandler.BackupPerformed(clockInSeconds)
   244  	}
   245  
   246  	return clockInSeconds, nil
   247  }
   248  
   249  func (m *Messenger) encodeAndDispatchBackupMessage(ctx context.Context, message *protobuf.Backup, chatID string) error {
   250  	encodedMessage, err := proto.Marshal(message)
   251  	if err != nil {
   252  		return err
   253  	}
   254  
   255  	_, err = m.dispatchMessage(ctx, common.RawMessage{
   256  		LocalChatID:         chatID,
   257  		Payload:             encodedMessage,
   258  		SkipEncryptionLayer: true,
   259  		SendOnPersonalTopic: true,
   260  		MessageType:         protobuf.ApplicationMetadataMessage_BACKUP,
   261  	})
   262  
   263  	return err
   264  }
   265  
   266  func (m *Messenger) backupContacts(ctx context.Context) []*protobuf.Backup {
   267  	var contacts []*protobuf.SyncInstallationContactV2
   268  	m.allContacts.Range(func(contactID string, contact *Contact) (shouldContinue bool) {
   269  		syncContact := m.buildSyncContactMessage(contact)
   270  		if syncContact != nil {
   271  			contacts = append(contacts, syncContact)
   272  		}
   273  		return true
   274  	})
   275  
   276  	var backupMessages []*protobuf.Backup
   277  	for i := 0; i < len(contacts); i += BackupContactsPerBatch {
   278  		j := i + BackupContactsPerBatch
   279  		if j > len(contacts) {
   280  			j = len(contacts)
   281  		}
   282  
   283  		contactsToAdd := contacts[i:j]
   284  
   285  		backupMessage := &protobuf.Backup{
   286  			Contacts: contactsToAdd,
   287  		}
   288  		backupMessages = append(backupMessages, backupMessage)
   289  	}
   290  
   291  	return backupMessages
   292  }
   293  
   294  func (m *Messenger) retrieveAllCommunities() (*CommunitySet, error) {
   295  	joinedCs, err := m.communitiesManager.JoinedAndPendingCommunitiesWithRequests()
   296  	if err != nil {
   297  		return nil, err
   298  	}
   299  
   300  	deletedCs, err := m.communitiesManager.DeletedCommunities()
   301  	if err != nil {
   302  		return nil, err
   303  	}
   304  
   305  	return &CommunitySet{
   306  		Joined:  joinedCs,
   307  		Deleted: deletedCs,
   308  	}, nil
   309  }
   310  
   311  func (m *Messenger) backupCommunities(ctx context.Context, clock uint64) ([]*protobuf.Backup, error) {
   312  	communitySet, err := m.retrieveAllCommunities()
   313  	if err != nil {
   314  		return nil, err
   315  	}
   316  
   317  	var backupMessages []*protobuf.Backup
   318  	combinedCs := append(communitySet.Joined, communitySet.Deleted...)
   319  
   320  	for _, c := range combinedCs {
   321  		_, beingImported := m.importingCommunities[c.IDString()]
   322  		if !beingImported {
   323  			backupMessage, err := m.backupCommunity(c, clock)
   324  			if err != nil {
   325  				return nil, err
   326  			}
   327  
   328  			backupMessages = append(backupMessages, backupMessage)
   329  		}
   330  	}
   331  
   332  	return backupMessages, nil
   333  }
   334  
   335  func (m *Messenger) backupCommunity(community *communities.Community, clock uint64) (*protobuf.Backup, error) {
   336  	communityId := community.ID()
   337  	settings, err := m.communitiesManager.GetCommunitySettingsByID(communityId)
   338  	if err != nil {
   339  		return nil, err
   340  	}
   341  
   342  	syncControlNode, err := m.communitiesManager.GetSyncControlNode(communityId)
   343  	if err != nil {
   344  		return nil, err
   345  	}
   346  
   347  	syncMessage, err := community.ToSyncInstallationCommunityProtobuf(clock, settings, syncControlNode)
   348  	if err != nil {
   349  		return nil, err
   350  	}
   351  
   352  	err = m.propagateSyncInstallationCommunityWithHRKeys(syncMessage, community)
   353  	if err != nil {
   354  		return nil, err
   355  	}
   356  
   357  	return &protobuf.Backup{
   358  		Communities: []*protobuf.SyncInstallationCommunity{syncMessage},
   359  	}, nil
   360  }
   361  
   362  func (m *Messenger) backupChats(ctx context.Context, clock uint64) []*protobuf.Backup {
   363  	var oneToOneAndGroupChats []*protobuf.SyncChat
   364  	m.allChats.Range(func(chatID string, chat *Chat) bool {
   365  		if !chat.OneToOne() && !chat.PrivateGroupChat() {
   366  			return true
   367  		}
   368  		syncChat := protobuf.SyncChat{
   369  			Clock:    clock,
   370  			Id:       chatID,
   371  			ChatType: uint32(chat.ChatType),
   372  			Active:   chat.Active,
   373  		}
   374  		chatMuteTill, _ := time.Parse(time.RFC3339, chat.MuteTill.Format(time.RFC3339))
   375  		if chat.Muted && chatMuteTill.Equal(time.Time{}) {
   376  			// Only set Muted if it is "permanently" muted
   377  			syncChat.Muted = true
   378  		}
   379  		if chat.PrivateGroupChat() {
   380  			syncChat.Name = chat.Name // The Name is only useful in the case of a group chat
   381  
   382  			syncChat.MembershipUpdateEvents = make([]*protobuf.MembershipUpdateEvents, len(chat.MembershipUpdates))
   383  			for i, membershipUpdate := range chat.MembershipUpdates {
   384  				syncChat.MembershipUpdateEvents[i] = &protobuf.MembershipUpdateEvents{
   385  					Clock:      membershipUpdate.ClockValue,
   386  					Type:       uint32(membershipUpdate.Type),
   387  					Members:    membershipUpdate.Members,
   388  					Name:       membershipUpdate.Name,
   389  					Signature:  membershipUpdate.Signature,
   390  					ChatId:     membershipUpdate.ChatID,
   391  					From:       membershipUpdate.From,
   392  					RawPayload: membershipUpdate.RawPayload,
   393  					Color:      membershipUpdate.Color,
   394  				}
   395  			}
   396  		}
   397  		oneToOneAndGroupChats = append(oneToOneAndGroupChats, &syncChat)
   398  		return true
   399  	})
   400  
   401  	var backupMessages []*protobuf.Backup
   402  	backupMessage := &protobuf.Backup{
   403  		Chats: oneToOneAndGroupChats,
   404  	}
   405  	backupMessages = append(backupMessages, backupMessage)
   406  	return backupMessages
   407  }
   408  
   409  func (m *Messenger) buildSyncContactMessage(contact *Contact) *protobuf.SyncInstallationContactV2 {
   410  	var ensName string
   411  	if contact.ENSVerified {
   412  		ensName = contact.EnsName
   413  	}
   414  
   415  	var customizationColor uint32
   416  	if len(contact.CustomizationColor) != 0 {
   417  		customizationColor = multiaccountscommon.ColorToIDFallbackToBlue(contact.CustomizationColor)
   418  	}
   419  
   420  	oneToOneChat, ok := m.allChats.Load(contact.ID)
   421  	muted := false
   422  	if ok {
   423  		muted = oneToOneChat.Muted
   424  	}
   425  
   426  	return &protobuf.SyncInstallationContactV2{
   427  		LastUpdatedLocally:        contact.LastUpdatedLocally,
   428  		LastUpdated:               contact.LastUpdated,
   429  		Id:                        contact.ID,
   430  		DisplayName:               contact.DisplayName,
   431  		EnsName:                   ensName,
   432  		CustomizationColor:        customizationColor,
   433  		LocalNickname:             contact.LocalNickname,
   434  		Added:                     contact.added(),
   435  		Blocked:                   contact.Blocked,
   436  		Muted:                     muted,
   437  		HasAddedUs:                contact.hasAddedUs(),
   438  		Removed:                   contact.Removed,
   439  		ContactRequestLocalState:  int64(contact.ContactRequestLocalState),
   440  		ContactRequestRemoteState: int64(contact.ContactRequestRemoteState),
   441  		ContactRequestRemoteClock: int64(contact.ContactRequestRemoteClock),
   442  		ContactRequestLocalClock:  int64(contact.ContactRequestLocalClock),
   443  		VerificationStatus:        int64(contact.VerificationStatus),
   444  		TrustStatus:               int64(contact.TrustStatus),
   445  	}
   446  }
   447  
   448  func (m *Messenger) backupProfile(ctx context.Context, clock uint64) ([]*protobuf.Backup, error) {
   449  	displayName, err := m.settings.DisplayName()
   450  	if err != nil {
   451  		return nil, err
   452  	}
   453  
   454  	displayNameClock, err := m.settings.GetSettingLastSynced(settings.DisplayName)
   455  	if err != nil {
   456  		return nil, err
   457  	}
   458  
   459  	if m.account == nil {
   460  		return nil, nil
   461  	}
   462  
   463  	keyUID := m.account.KeyUID
   464  	images, err := m.multiAccounts.GetIdentityImages(keyUID)
   465  	if err != nil {
   466  		return nil, err
   467  	}
   468  
   469  	pictureProtos := make([]*protobuf.SyncProfilePicture, len(images))
   470  	for i, image := range images {
   471  		p := &protobuf.SyncProfilePicture{}
   472  		p.Name = image.Name
   473  		p.Payload = image.Payload
   474  		p.Width = uint32(image.Width)
   475  		p.Height = uint32(image.Height)
   476  		p.FileSize = uint32(image.FileSize)
   477  		p.ResizeTarget = uint32(image.ResizeTarget)
   478  		if image.Clock == 0 {
   479  			p.Clock = clock
   480  		} else {
   481  			p.Clock = image.Clock
   482  		}
   483  		pictureProtos[i] = p
   484  	}
   485  
   486  	ensUsernameDetails, err := m.getEnsUsernameDetails()
   487  	if err != nil {
   488  		return nil, err
   489  	}
   490  	ensUsernameDetailProtos := make([]*protobuf.SyncEnsUsernameDetail, len(ensUsernameDetails))
   491  	for i, ensUsernameDetail := range ensUsernameDetails {
   492  		ensUsernameDetailProtos[i] = &protobuf.SyncEnsUsernameDetail{
   493  			Username: ensUsernameDetail.Username,
   494  			Clock:    ensUsernameDetail.Clock,
   495  			Removed:  ensUsernameDetail.Removed,
   496  			ChainId:  ensUsernameDetail.ChainID,
   497  		}
   498  	}
   499  
   500  	profileShowcasePreferences, err := m.GetProfileShowcasePreferences()
   501  	if err != nil {
   502  		return nil, err
   503  	}
   504  
   505  	backupMessage := &protobuf.Backup{
   506  		Profile: &protobuf.BackedUpProfile{
   507  			KeyUid:                     keyUID,
   508  			DisplayName:                displayName,
   509  			Pictures:                   pictureProtos,
   510  			DisplayNameClock:           displayNameClock,
   511  			EnsUsernameDetails:         ensUsernameDetailProtos,
   512  			ProfileShowcasePreferences: ToProfileShowcasePreferencesProto(profileShowcasePreferences),
   513  		},
   514  	}
   515  
   516  	backupMessages := []*protobuf.Backup{backupMessage}
   517  
   518  	return backupMessages, nil
   519  }
   520  
   521  func (m *Messenger) backupKeypairs() ([]*protobuf.Backup, error) {
   522  	keypairs, err := m.settings.GetAllKeypairs()
   523  	if err != nil {
   524  		return nil, err
   525  	}
   526  
   527  	var backupMessages []*protobuf.Backup
   528  	for _, kp := range keypairs {
   529  
   530  		kp.SyncedFrom = accounts.SyncedFromBackup
   531  		keypair, err := m.prepareSyncKeypairMessage(kp)
   532  		if err != nil {
   533  			return nil, err
   534  		}
   535  
   536  		backupMessage := &protobuf.Backup{
   537  			Keypair: keypair,
   538  		}
   539  
   540  		backupMessages = append(backupMessages, backupMessage)
   541  	}
   542  
   543  	return backupMessages, nil
   544  }
   545  
   546  func (m *Messenger) backupWatchOnlyAccounts() ([]*protobuf.Backup, error) {
   547  	accounts, err := m.settings.GetAllWatchOnlyAccounts()
   548  	if err != nil {
   549  		return nil, err
   550  	}
   551  
   552  	var backupMessages []*protobuf.Backup
   553  	for _, acc := range accounts {
   554  
   555  		backupMessage := &protobuf.Backup{}
   556  		backupMessage.WatchOnlyAccount = m.prepareSyncAccountMessage(acc)
   557  
   558  		backupMessages = append(backupMessages, backupMessage)
   559  	}
   560  
   561  	return backupMessages, nil
   562  }