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 }