github.com/status-im/status-go@v1.1.0/protocol/messenger_mailserver.go (about) 1 package protocol 2 3 import ( 4 "context" 5 "fmt" 6 "math" 7 "sort" 8 "sync" 9 "time" 10 11 "github.com/libp2p/go-libp2p/core/peer" 12 "github.com/pkg/errors" 13 "go.uber.org/zap" 14 15 "github.com/status-im/status-go/connection" 16 "github.com/status-im/status-go/eth-node/crypto" 17 "github.com/status-im/status-go/eth-node/types" 18 "github.com/status-im/status-go/protocol/common" 19 "github.com/status-im/status-go/protocol/protobuf" 20 "github.com/status-im/status-go/protocol/transport" 21 "github.com/status-im/status-go/services/mailservers" 22 ) 23 24 const ( 25 initialStoreNodeRequestPageSize = 4 26 defaultStoreNodeRequestPageSize = 50 27 28 // tolerance is how many seconds of potentially out-of-order messages we want to fetch 29 tolerance uint32 = 60 30 31 mailserverRequestTimeout = 30 * time.Second 32 mailserverMaxTries uint = 2 33 mailserverMaxFailedRequests uint = 2 34 35 oneDayDuration = 24 * time.Hour 36 oneMonthDuration = 31 * oneDayDuration 37 ) 38 39 // maxTopicsPerRequest sets the batch size to limit the number of topics per store query 40 var maxTopicsPerRequest int = 10 41 42 var ErrNoFiltersForChat = errors.New("no filter registered for given chat") 43 44 func (m *Messenger) shouldSync() (bool, error) { 45 // TODO (pablo) support community store node as well 46 if m.mailserverCycle.activeMailserver == nil || !m.Online() { 47 return false, nil 48 } 49 50 useMailserver, err := m.settings.CanUseMailservers() 51 if err != nil { 52 m.logger.Error("failed to get use mailservers", zap.Error(err)) 53 return false, err 54 } 55 56 return useMailserver, nil 57 } 58 59 func (m *Messenger) scheduleSyncChat(chat *Chat) (bool, error) { 60 shouldSync, err := m.shouldSync() 61 if err != nil { 62 m.logger.Error("failed to get should sync", zap.Error(err)) 63 return false, err 64 } 65 66 if !shouldSync { 67 return false, nil 68 } 69 70 go func() { 71 ms := m.getActiveMailserver(chat.CommunityID) 72 _, err = m.performMailserverRequest(ms, func(mailServer mailservers.Mailserver) (*MessengerResponse, error) { 73 response, err := m.syncChatWithFilters(mailServer, chat.ID) 74 75 if err != nil { 76 m.logger.Error("failed to sync chat", zap.Error(err)) 77 return nil, err 78 } 79 80 if m.config.messengerSignalsHandler != nil { 81 m.config.messengerSignalsHandler.MessengerResponse(response) 82 } 83 return response, nil 84 }) 85 if err != nil { 86 m.logger.Error("failed to perform mailserver request", zap.Error(err)) 87 } 88 }() 89 return true, nil 90 } 91 92 func (m *Messenger) connectToNewMailserverAndWait() error { 93 // Handle pinned mailservers 94 m.logger.Info("disconnecting mailserver") 95 pinnedMailserver, err := m.getPinnedMailserver() 96 if err != nil { 97 m.logger.Error("could not obtain the pinned mailserver", zap.Error(err)) 98 return err 99 } 100 // If pinned mailserver is not nil, no need to disconnect and wait for it to be available 101 if pinnedMailserver == nil { 102 m.disconnectActiveMailserver(graylistBackoff) 103 } 104 105 return m.findNewMailserver() 106 } 107 108 func (m *Messenger) performMailserverRequest(ms *mailservers.Mailserver, fn func(mailServer mailservers.Mailserver) (*MessengerResponse, error)) (*MessengerResponse, error) { 109 if ms == nil { 110 return nil, errors.New("mailserver not available") 111 } 112 113 m.mailserverCycle.RLock() 114 defer m.mailserverCycle.RUnlock() 115 var tries uint = 0 116 for tries < mailserverMaxTries { 117 if !m.communityStorenodes.IsCommunityStoreNode(ms.ID) && !m.isMailserverAvailable(ms.ID) { 118 return nil, errors.New("storenode not available") 119 } 120 m.logger.Info("trying performing mailserver requests", zap.Uint("try", tries), zap.String("mailserverID", ms.ID)) 121 122 // Peform request 123 response, err := fn(*ms) // pass by value because we don't want the fn to modify the mailserver 124 if err == nil { 125 // Reset failed requests 126 m.logger.Debug("mailserver request performed successfully", 127 zap.String("mailserverID", ms.ID)) 128 ms.FailedRequests = 0 129 return response, nil 130 } 131 132 m.logger.Error("failed to perform mailserver request", 133 zap.String("mailserverID", ms.ID), 134 zap.Uint("tries", tries), 135 zap.Error(err), 136 ) 137 138 tries++ 139 // Increment failed requests 140 ms.FailedRequests++ 141 142 // Change mailserver 143 if ms.FailedRequests >= mailserverMaxFailedRequests { 144 return nil, errors.New("too many failed requests") 145 } 146 // Wait a couple of second not to spam 147 time.Sleep(2 * time.Second) 148 149 } 150 return nil, errors.New("failed to perform mailserver request") 151 } 152 153 func (m *Messenger) scheduleSyncFilters(filters []*transport.Filter) (bool, error) { 154 shouldSync, err := m.shouldSync() 155 if err != nil { 156 m.logger.Error("failed to get shouldSync", zap.Error(err)) 157 return false, err 158 } 159 160 if !shouldSync { 161 return false, nil 162 } 163 164 go func() { 165 // split filters by community store node so we can request the filters to the correct mailserver 166 filtersByMs := m.SplitFiltersByStoreNode(filters) 167 for communityID, filtersForMs := range filtersByMs { 168 ms := m.getActiveMailserver(communityID) 169 _, err := m.performMailserverRequest(ms, func(ms mailservers.Mailserver) (*MessengerResponse, error) { 170 response, err := m.syncFilters(ms, filtersForMs) 171 172 if err != nil { 173 m.logger.Error("failed to sync filter", zap.Error(err)) 174 return nil, err 175 } 176 177 if m.config.messengerSignalsHandler != nil { 178 m.config.messengerSignalsHandler.MessengerResponse(response) 179 } 180 return response, nil 181 }) 182 if err != nil { 183 m.logger.Error("failed to perform mailserver request", zap.Error(err)) 184 } 185 } 186 187 }() 188 return true, nil 189 } 190 191 func (m *Messenger) calculateMailserverTo() uint32 { 192 seconds := float64(m.GetCurrentTimeInMillis()) / 1000 193 return uint32(math.Ceil(seconds)) 194 } 195 196 func (m *Messenger) calculateMailserverTimeBounds(duration time.Duration) (uint32, uint32) { 197 now := float64(m.GetCurrentTimeInMillis()) / 1000 198 to := uint32(math.Ceil(now)) 199 from := uint32(math.Floor(now)) - uint32(duration.Seconds()) 200 return from, to 201 } 202 203 func (m *Messenger) filtersForChat(chatID string) ([]*transport.Filter, error) { 204 chat, ok := m.allChats.Load(chatID) 205 if !ok { 206 return nil, ErrChatNotFound 207 } 208 var filters []*transport.Filter 209 210 if chat.OneToOne() { 211 // We sync our own topic and any eventual negotiated 212 publicKeys := []string{common.PubkeyToHex(&m.identity.PublicKey), chatID} 213 214 filters = m.transport.FiltersByIdentities(publicKeys) 215 216 } else if chat.PrivateGroupChat() { 217 var publicKeys []string 218 for _, m := range chat.Members { 219 publicKeys = append(publicKeys, m.ID) 220 } 221 222 filters = m.transport.FiltersByIdentities(publicKeys) 223 224 } else { 225 filter := m.transport.FilterByChatID(chatID) 226 if filter == nil { 227 return nil, ErrNoFiltersForChat 228 } 229 filters = []*transport.Filter{filter} 230 } 231 232 return filters, nil 233 } 234 235 func (m *Messenger) topicsForChat(chatID string) (string, []types.TopicType, error) { 236 filters, err := m.filtersForChat(chatID) 237 if err != nil { 238 return "", nil, err 239 } 240 241 var contentTopics []types.TopicType 242 243 for _, filter := range filters { 244 contentTopics = append(contentTopics, filter.ContentTopic) 245 } 246 247 return filters[0].PubsubTopic, contentTopics, nil 248 } 249 250 func (m *Messenger) syncChatWithFilters(ms mailservers.Mailserver, chatID string) (*MessengerResponse, error) { 251 filters, err := m.filtersForChat(chatID) 252 if err != nil { 253 return nil, err 254 } 255 256 return m.syncFilters(ms, filters) 257 } 258 259 func (m *Messenger) syncBackup() error { 260 261 filter := m.transport.PersonalTopicFilter() 262 if filter == nil { 263 return errors.New("personal topic filter not loaded") 264 } 265 canSync, err := m.canSyncWithStoreNodes() 266 if err != nil { 267 return err 268 } 269 if !canSync { 270 return nil 271 } 272 273 from, to := m.calculateMailserverTimeBounds(oneMonthDuration) 274 275 batch := MailserverBatch{From: from, To: to, Topics: []types.TopicType{filter.ContentTopic}} 276 ms := m.getActiveMailserver(filter.ChatID) 277 err = m.processMailserverBatch(*ms, batch) 278 if err != nil { 279 return err 280 } 281 return m.settings.SetBackupFetched(true) 282 } 283 284 func (m *Messenger) defaultSyncPeriodFromNow() (uint32, error) { 285 defaultSyncPeriod, err := m.settings.GetDefaultSyncPeriod() 286 if err != nil { 287 return 0, err 288 } 289 return uint32(m.getTimesource().GetCurrentTime()/1000) - defaultSyncPeriod, nil 290 } 291 292 // capToDefaultSyncPeriod caps the sync period to the default 293 func (m *Messenger) capToDefaultSyncPeriod(period uint32) (uint32, error) { 294 d, err := m.defaultSyncPeriodFromNow() 295 if err != nil { 296 return 0, err 297 } 298 if d > period { 299 return d, nil 300 } 301 return period - tolerance, nil 302 } 303 304 func (m *Messenger) updateFiltersPriority(filters []*transport.Filter) { 305 for _, filter := range filters { 306 chatID := filter.ChatID 307 chat := m.Chat(chatID) 308 if chat != nil { 309 filter.Priority = chat.ReadMessagesAtClockValue 310 } 311 } 312 } 313 314 func (m *Messenger) resetFiltersPriority(filters []*transport.Filter) { 315 for _, filter := range filters { 316 filter.Priority = 0 317 } 318 } 319 320 func (m *Messenger) SplitFiltersByStoreNode(filters []*transport.Filter) map[string][]*transport.Filter { 321 // split filters by community store node so we can request the filters to the correct mailserver 322 filtersByMs := make(map[string][]*transport.Filter, len(filters)) 323 for _, f := range filters { 324 communityID := "" // none by default 325 if chat, ok := m.allChats.Load(f.ChatID); ok && chat.CommunityChat() && m.communityStorenodes.HasStorenodeSetup(chat.CommunityID) { 326 communityID = chat.CommunityID 327 } 328 if _, exists := filtersByMs[communityID]; !exists { 329 filtersByMs[communityID] = make([]*transport.Filter, 0, len(filters)) 330 } 331 filtersByMs[communityID] = append(filtersByMs[communityID], f) 332 } 333 return filtersByMs 334 } 335 336 // RequestAllHistoricMessages requests all the historic messages for any topic 337 func (m *Messenger) RequestAllHistoricMessages(forceFetchingBackup, withRetries bool) (*MessengerResponse, error) { 338 shouldSync, err := m.shouldSync() 339 if err != nil { 340 return nil, err 341 } 342 343 if !shouldSync { 344 return nil, nil 345 } 346 347 backupFetched, err := m.settings.BackupFetched() 348 if err != nil { 349 return nil, err 350 } 351 352 if m.mailserversDatabase == nil { 353 return nil, nil 354 } 355 356 if forceFetchingBackup || !backupFetched { 357 m.logger.Info("fetching backup") 358 err := m.syncBackup() 359 if err != nil { 360 return nil, err 361 } 362 m.logger.Info("backup fetched") 363 } 364 365 filters := m.transport.Filters() 366 m.updateFiltersPriority(filters) 367 defer m.resetFiltersPriority(filters) 368 369 filtersByMs := m.SplitFiltersByStoreNode(filters) 370 allResponses := &MessengerResponse{} 371 for communityID, filtersForMs := range filtersByMs { 372 ms := m.getActiveMailserver(communityID) 373 if withRetries { 374 response, err := m.performMailserverRequest(ms, func(ms mailservers.Mailserver) (*MessengerResponse, error) { 375 return m.syncFilters(ms, filtersForMs) 376 }) 377 if err != nil { 378 return nil, err 379 } 380 if response != nil { 381 allResponses.AddChats(response.Chats()) 382 allResponses.AddMessages(response.Messages()) 383 } 384 continue 385 } 386 response, err := m.syncFilters(*ms, filtersForMs) 387 if err != nil { 388 return nil, err 389 } 390 if response != nil { 391 allResponses.AddChats(response.Chats()) 392 allResponses.AddMessages(response.Messages()) 393 } 394 } 395 return allResponses, nil 396 } 397 398 const missingMessageCheckPeriod = 30 * time.Second 399 400 func (m *Messenger) checkForMissingMessagesLoop() { 401 t := time.NewTicker(missingMessageCheckPeriod) 402 defer t.Stop() 403 404 mailserverAvailableSignal := m.mailserverCycle.availabilitySubscriptions.Subscribe() 405 406 for { 407 select { 408 case <-m.quit: 409 return 410 411 // Wait for mailserver available, also triggered on mailserver change 412 case <-mailserverAvailableSignal: 413 mailserverAvailableSignal = m.mailserverCycle.availabilitySubscriptions.Subscribe() 414 415 case <-t.C: 416 417 } 418 419 filters := m.transport.Filters() 420 filtersByMs := m.SplitFiltersByStoreNode(filters) 421 for communityID, filtersForMs := range filtersByMs { 422 ms := m.getActiveMailserver(communityID) 423 if ms == nil { 424 continue 425 } 426 427 peerID, err := ms.PeerID() 428 if err != nil { 429 m.logger.Error("could not obtain the peerID") 430 return 431 } 432 m.transport.SetCriteriaForMissingMessageVerification(peerID, filtersForMs) 433 } 434 } 435 } 436 437 func getPrioritizedBatches() []int { 438 return []int{1, 5, 10} 439 } 440 441 func (m *Messenger) syncFiltersFrom(ms mailservers.Mailserver, filters []*transport.Filter, lastRequest uint32) (*MessengerResponse, error) { 442 canSync, err := m.canSyncWithStoreNodes() 443 if err != nil { 444 return nil, err 445 } 446 if !canSync { 447 return nil, nil 448 } 449 450 response := &MessengerResponse{} 451 topicInfo, err := m.mailserversDatabase.Topics() 452 if err != nil { 453 return nil, err 454 } 455 456 topicsData := make(map[string]mailservers.MailserverTopic) 457 for _, topic := range topicInfo { 458 topicsData[fmt.Sprintf("%s-%s", topic.PubsubTopic, topic.ContentTopic)] = topic 459 } 460 461 batches := make(map[string]map[int]MailserverBatch) 462 463 to := m.calculateMailserverTo() 464 var syncedTopics []mailservers.MailserverTopic 465 466 sort.Slice(filters[:], func(i, j int) bool { 467 p1 := filters[i].Priority 468 p2 := filters[j].Priority 469 return p1 > p2 470 }) 471 prioritizedBatches := getPrioritizedBatches() 472 currentBatch := 0 473 474 if len(filters) == 0 || filters[0].Priority == 0 { 475 currentBatch = len(prioritizedBatches) 476 } 477 478 defaultPeriodFromNow, err := m.defaultSyncPeriodFromNow() 479 if err != nil { 480 return nil, err 481 } 482 483 contentTopicsPerPubsubTopic := make(map[string]map[string]*transport.Filter) 484 for _, filter := range filters { 485 if !filter.Listen || filter.Ephemeral { 486 continue 487 } 488 489 contentTopics, ok := contentTopicsPerPubsubTopic[filter.PubsubTopic] 490 if !ok { 491 contentTopics = make(map[string]*transport.Filter) 492 } 493 contentTopics[filter.ContentTopic.String()] = filter 494 contentTopicsPerPubsubTopic[filter.PubsubTopic] = contentTopics 495 } 496 497 for pubsubTopic, contentTopics := range contentTopicsPerPubsubTopic { 498 if _, ok := batches[pubsubTopic]; !ok { 499 batches[pubsubTopic] = make(map[int]MailserverBatch) 500 } 501 502 for _, filter := range contentTopics { 503 var chatID string 504 // If the filter has an identity, we use it as a chatID, otherwise is a public chat/community chat filter 505 if len(filter.Identity) != 0 { 506 chatID = filter.Identity 507 } else { 508 chatID = filter.ChatID 509 } 510 511 topicData, ok := topicsData[fmt.Sprintf("%s-%s", filter.PubsubTopic, filter.ContentTopic)] 512 var capToDefaultSyncPeriod = true 513 if !ok { 514 if lastRequest == 0 { 515 lastRequest = defaultPeriodFromNow 516 } 517 topicData = mailservers.MailserverTopic{ 518 PubsubTopic: filter.PubsubTopic, 519 ContentTopic: filter.ContentTopic.String(), 520 LastRequest: int(defaultPeriodFromNow), 521 } 522 } else if lastRequest != 0 { 523 topicData.LastRequest = int(lastRequest) 524 capToDefaultSyncPeriod = false 525 } 526 527 batchID := topicData.LastRequest 528 529 if currentBatch < len(prioritizedBatches) { 530 batch, ok := batches[pubsubTopic][currentBatch] 531 if ok { 532 prevTopicData, ok := topicsData[batch.PubsubTopic+batch.Topics[0].String()] 533 if (!ok && topicData.LastRequest != int(defaultPeriodFromNow)) || 534 (ok && prevTopicData.LastRequest != topicData.LastRequest) { 535 currentBatch++ 536 } 537 } 538 if currentBatch < len(prioritizedBatches) { 539 batchID = currentBatch 540 currentBatchCap := prioritizedBatches[currentBatch] - 1 541 if currentBatchCap == 0 { 542 currentBatch++ 543 } else { 544 prioritizedBatches[currentBatch] = currentBatchCap 545 } 546 } 547 } 548 549 batch, ok := batches[pubsubTopic][batchID] 550 if !ok { 551 from := uint32(topicData.LastRequest) 552 if capToDefaultSyncPeriod { 553 from, err = m.capToDefaultSyncPeriod(uint32(topicData.LastRequest)) 554 if err != nil { 555 return nil, err 556 } 557 } 558 batch = MailserverBatch{From: from, To: to} 559 } 560 561 batch.ChatIDs = append(batch.ChatIDs, chatID) 562 batch.PubsubTopic = pubsubTopic 563 batch.Topics = append(batch.Topics, filter.ContentTopic) 564 batches[pubsubTopic][batchID] = batch 565 566 // Set last request to the new `to` 567 topicData.LastRequest = int(to) 568 syncedTopics = append(syncedTopics, topicData) 569 } 570 } 571 572 if m.config.messengerSignalsHandler != nil { 573 m.config.messengerSignalsHandler.HistoryRequestStarted(len(batches)) 574 } 575 576 var batches24h []MailserverBatch 577 for pubsubTopic := range batches { 578 batchKeys := make([]int, 0, len(batches[pubsubTopic])) 579 for k := range batches[pubsubTopic] { 580 batchKeys = append(batchKeys, k) 581 } 582 sort.Ints(batchKeys) 583 584 keysToIterate := append([]int{}, batchKeys...) 585 for { 586 // For all batches 587 var tmpKeysToIterate []int 588 for _, k := range keysToIterate { 589 batch := batches[pubsubTopic][k] 590 591 dayBatch := MailserverBatch{ 592 To: batch.To, 593 Cursor: batch.Cursor, 594 PubsubTopic: batch.PubsubTopic, 595 Topics: batch.Topics, 596 ChatIDs: batch.ChatIDs, 597 } 598 599 from := batch.To - uint32(oneDayDuration.Seconds()) 600 if from > batch.From { 601 dayBatch.From = from 602 batches24h = append(batches24h, dayBatch) 603 604 // Replace og batch with new dates 605 batch.To = from 606 batches[pubsubTopic][k] = batch 607 tmpKeysToIterate = append(tmpKeysToIterate, k) 608 } else { 609 batches24h = append(batches24h, batch) 610 } 611 } 612 613 if len(tmpKeysToIterate) == 0 { 614 break 615 } 616 keysToIterate = tmpKeysToIterate 617 } 618 } 619 620 for _, batch := range batches24h { 621 err := m.processMailserverBatch(ms, batch) 622 if err != nil { 623 m.logger.Error("error syncing topics", zap.Error(err)) 624 return nil, err 625 } 626 } 627 628 m.logger.Debug("topics synced") 629 if m.config.messengerSignalsHandler != nil { 630 m.config.messengerSignalsHandler.HistoryRequestCompleted() 631 } 632 633 err = m.mailserversDatabase.AddTopics(syncedTopics) 634 if err != nil { 635 return nil, err 636 } 637 638 var messagesToBeSaved []*common.Message 639 for _, batches := range batches { 640 for _, batch := range batches { 641 for _, id := range batch.ChatIDs { 642 chat, ok := m.allChats.Load(id) 643 if !ok || !chat.Active || chat.Timeline() || chat.ProfileUpdates() { 644 continue 645 } 646 gap, err := m.calculateGapForChat(chat, batch.From) 647 if err != nil { 648 return nil, err 649 } 650 if chat.SyncedFrom == 0 || chat.SyncedFrom > batch.From { 651 chat.SyncedFrom = batch.From 652 } 653 654 chat.SyncedTo = to 655 656 err = m.persistence.SetSyncTimestamps(chat.SyncedFrom, chat.SyncedTo, chat.ID) 657 if err != nil { 658 return nil, err 659 } 660 661 response.AddChat(chat) 662 if gap != nil { 663 response.AddMessage(gap) 664 messagesToBeSaved = append(messagesToBeSaved, gap) 665 } 666 } 667 } 668 } 669 670 if len(messagesToBeSaved) > 0 { 671 err := m.persistence.SaveMessages(messagesToBeSaved) 672 if err != nil { 673 return nil, err 674 } 675 } 676 return response, nil 677 } 678 679 func (m *Messenger) syncFilters(ms mailservers.Mailserver, filters []*transport.Filter) (*MessengerResponse, error) { 680 return m.syncFiltersFrom(ms, filters, 0) 681 } 682 683 func (m *Messenger) calculateGapForChat(chat *Chat, from uint32) (*common.Message, error) { 684 // Chat was never synced, no gap necessary 685 if chat.SyncedTo == 0 { 686 return nil, nil 687 } 688 689 // If we filled the gap, nothing to do 690 if chat.SyncedTo >= from { 691 return nil, nil 692 } 693 694 timestamp := m.getTimesource().GetCurrentTime() 695 696 message := &common.Message{ 697 ChatMessage: &protobuf.ChatMessage{ 698 ChatId: chat.ID, 699 Text: "Gap message", 700 MessageType: protobuf.MessageType_SYSTEM_MESSAGE_GAP, 701 ContentType: protobuf.ChatMessage_SYSTEM_MESSAGE_GAP, 702 Clock: uint64(from) * 1000, 703 Timestamp: timestamp, 704 }, 705 GapParameters: &common.GapParameters{ 706 From: chat.SyncedTo, 707 To: from, 708 }, 709 From: common.PubkeyToHex(&m.identity.PublicKey), 710 WhisperTimestamp: timestamp, 711 LocalChatID: chat.ID, 712 Seen: true, 713 ID: types.EncodeHex(crypto.Keccak256([]byte(fmt.Sprintf("%s-%d-%d", chat.ID, chat.SyncedTo, from)))), 714 } 715 716 return message, m.persistence.SaveMessages([]*common.Message{message}) 717 } 718 719 type work struct { 720 pubsubTopic string 721 contentTopics []types.TopicType 722 cursor types.StoreRequestCursor 723 limit uint32 724 } 725 726 type messageRequester interface { 727 SendMessagesRequestForTopics( 728 ctx context.Context, 729 peerID peer.ID, 730 from, to uint32, 731 previousStoreCursor types.StoreRequestCursor, 732 pubsubTopic string, 733 contentTopics []types.TopicType, 734 limit uint32, 735 waitForResponse bool, 736 processEnvelopes bool, 737 ) (cursor types.StoreRequestCursor, envelopesCount int, err error) 738 } 739 740 func processMailserverBatch( 741 ctx context.Context, 742 messageRequester messageRequester, 743 batch MailserverBatch, 744 storenodeID peer.ID, 745 logger *zap.Logger, 746 pageLimit uint32, 747 shouldProcessNextPage func(int) (bool, uint32), 748 processEnvelopes bool, 749 ) error { 750 751 var topicStrings []string 752 for _, t := range batch.Topics { 753 topicStrings = append(topicStrings, t.String()) 754 } 755 logger = logger.With(zap.Any("chatIDs", batch.ChatIDs), 756 zap.String("fromString", time.Unix(int64(batch.From), 0).Format(time.RFC3339)), 757 zap.String("toString", time.Unix(int64(batch.To), 0).Format(time.RFC3339)), 758 zap.Any("topic", topicStrings), 759 zap.Int64("from", int64(batch.From)), 760 zap.Int64("to", int64(batch.To))) 761 762 logger.Info("syncing topic") 763 764 wg := sync.WaitGroup{} 765 workWg := sync.WaitGroup{} 766 workCh := make(chan work, 1000) // each batch item is split in 10 topics bunch and sent to this channel 767 workCompleteCh := make(chan struct{}) // once all batch items are processed, this channel is triggered 768 semaphore := make(chan int, 3) // limit the number of concurrent queries 769 errCh := make(chan error) 770 771 ctx, cancel := context.WithCancel(ctx) 772 defer cancel() 773 774 // Producer 775 wg.Add(1) 776 go func() { 777 defer func() { 778 logger.Debug("mailserver batch producer complete") 779 wg.Done() 780 }() 781 782 allWorks := int(math.Ceil(float64(len(batch.Topics)) / float64(maxTopicsPerRequest))) 783 workWg.Add(allWorks) 784 785 for i := 0; i < len(batch.Topics); i += maxTopicsPerRequest { 786 j := i + maxTopicsPerRequest 787 if j > len(batch.Topics) { 788 j = len(batch.Topics) 789 } 790 791 select { 792 case <-ctx.Done(): 793 logger.Debug("processBatch producer - context done") 794 return 795 default: 796 logger.Debug("processBatch producer - creating work") 797 workCh <- work{ 798 pubsubTopic: batch.PubsubTopic, 799 contentTopics: batch.Topics[i:j], 800 limit: pageLimit, 801 } 802 time.Sleep(50 * time.Millisecond) 803 } 804 } 805 806 go func() { 807 workWg.Wait() 808 workCompleteCh <- struct{}{} 809 }() 810 811 logger.Debug("processBatch producer complete") 812 }() 813 814 var result error 815 816 loop: 817 for { 818 select { 819 case <-ctx.Done(): 820 logger.Debug("processBatch cleanup - context done") 821 result = ctx.Err() 822 if errors.Is(result, context.Canceled) { 823 result = nil 824 } 825 break loop 826 case w, ok := <-workCh: 827 if !ok { 828 continue 829 } 830 831 logger.Debug("processBatch - received work") 832 semaphore <- 1 833 go func(w work) { // Consumer 834 defer func() { 835 workWg.Done() 836 <-semaphore 837 }() 838 839 queryCtx, queryCancel := context.WithTimeout(ctx, mailserverRequestTimeout) 840 cursor, envelopesCount, err := messageRequester.SendMessagesRequestForTopics(queryCtx, storenodeID, batch.From, batch.To, w.cursor, w.pubsubTopic, w.contentTopics, w.limit, true, processEnvelopes) 841 queryCancel() 842 843 if err != nil { 844 logger.Debug("failed to send request", zap.Error(err)) 845 errCh <- err 846 return 847 } 848 849 processNextPage := true 850 nextPageLimit := pageLimit 851 852 if shouldProcessNextPage != nil { 853 processNextPage, nextPageLimit = shouldProcessNextPage(envelopesCount) 854 } 855 856 if !processNextPage { 857 return 858 } 859 860 // Check the cursor after calling `shouldProcessNextPage`. 861 // The app might use process the fetched envelopes in the callback for own needs. 862 if cursor == nil { 863 return 864 } 865 866 logger.Debug("processBatch producer - creating work (cursor)") 867 868 workWg.Add(1) 869 workCh <- work{ 870 pubsubTopic: w.pubsubTopic, 871 contentTopics: w.contentTopics, 872 cursor: cursor, 873 limit: nextPageLimit, 874 } 875 }(w) 876 case err := <-errCh: 877 logger.Debug("processBatch - received error", zap.Error(err)) 878 cancel() // Kill go routines 879 return err 880 case <-workCompleteCh: 881 logger.Debug("processBatch - all jobs complete") 882 cancel() // Kill go routines 883 } 884 } 885 886 wg.Wait() 887 888 // NOTE(camellos): Disabling for now, not critical and I'd rather take a bit more time 889 // to test it 890 //logger.Info("waiting until message processed") 891 //m.waitUntilP2PMessagesProcessed() 892 893 logger.Info("synced topic", zap.NamedError("hasError", result)) 894 return result 895 } 896 897 func (m *Messenger) canSyncWithStoreNodes() (bool, error) { 898 if m.featureFlags.StoreNodesDisabled { 899 return false, nil 900 } 901 if m.connectionState.IsExpensive() { 902 return m.settings.CanSyncOnMobileNetwork() 903 } 904 905 return true, nil 906 } 907 908 func (m *Messenger) DisableStoreNodes() { 909 m.featureFlags.StoreNodesDisabled = true 910 } 911 912 func (m *Messenger) processMailserverBatch(ms mailservers.Mailserver, batch MailserverBatch) error { 913 canSync, err := m.canSyncWithStoreNodes() 914 if err != nil { 915 return err 916 } 917 if !canSync { 918 return nil 919 } 920 921 mailserverID, err := ms.PeerID() 922 if err != nil { 923 return err 924 } 925 logger := m.logger.With(zap.String("mailserverID", ms.ID)) 926 return processMailserverBatch(m.ctx, m.transport, batch, mailserverID, logger, defaultStoreNodeRequestPageSize, nil, false) 927 } 928 929 func (m *Messenger) processMailserverBatchWithOptions(ms mailservers.Mailserver, batch MailserverBatch, pageLimit uint32, shouldProcessNextPage func(int) (bool, uint32), processEnvelopes bool) error { 930 canSync, err := m.canSyncWithStoreNodes() 931 if err != nil { 932 return err 933 } 934 if !canSync { 935 return nil 936 } 937 938 mailserverID, err := ms.PeerID() 939 if err != nil { 940 return err 941 } 942 logger := m.logger.With(zap.String("mailserverID", ms.ID)) 943 return processMailserverBatch(m.ctx, m.transport, batch, mailserverID, logger, pageLimit, shouldProcessNextPage, processEnvelopes) 944 } 945 946 type MailserverBatch struct { 947 From uint32 948 To uint32 949 Cursor string 950 PubsubTopic string 951 Topics []types.TopicType 952 ChatIDs []string 953 } 954 955 func (m *Messenger) SyncChatFromSyncedFrom(chatID string) (uint32, error) { 956 chat, ok := m.allChats.Load(chatID) 957 if !ok { 958 return 0, ErrChatNotFound 959 } 960 961 ms := m.getActiveMailserver(chat.CommunityID) 962 var from uint32 963 _, err := m.performMailserverRequest(ms, func(ms mailservers.Mailserver) (*MessengerResponse, error) { 964 canSync, err := m.canSyncWithStoreNodes() 965 if err != nil { 966 return nil, err 967 } 968 if !canSync { 969 return nil, nil 970 } 971 972 pubsubTopic, topics, err := m.topicsForChat(chatID) 973 if err != nil { 974 return nil, nil 975 } 976 977 defaultSyncPeriod, err := m.settings.GetDefaultSyncPeriod() 978 if err != nil { 979 return nil, err 980 } 981 982 batch := MailserverBatch{ 983 ChatIDs: []string{chatID}, 984 To: chat.SyncedFrom, 985 From: chat.SyncedFrom - defaultSyncPeriod, 986 PubsubTopic: pubsubTopic, 987 Topics: topics, 988 } 989 if m.config.messengerSignalsHandler != nil { 990 m.config.messengerSignalsHandler.HistoryRequestStarted(1) 991 } 992 993 err = m.processMailserverBatch(ms, batch) 994 if err != nil { 995 return nil, err 996 } 997 998 if m.config.messengerSignalsHandler != nil { 999 m.config.messengerSignalsHandler.HistoryRequestCompleted() 1000 } 1001 if chat.SyncedFrom == 0 || chat.SyncedFrom > batch.From { 1002 chat.SyncedFrom = batch.From 1003 } 1004 1005 m.logger.Debug("setting sync timestamps", zap.Int64("from", int64(batch.From)), zap.Int64("to", int64(chat.SyncedTo)), zap.String("chatID", chatID)) 1006 1007 err = m.persistence.SetSyncTimestamps(batch.From, chat.SyncedTo, chat.ID) 1008 from = batch.From 1009 return nil, err 1010 }) 1011 if err != nil { 1012 return 0, err 1013 } 1014 1015 return from, nil 1016 } 1017 1018 func (m *Messenger) FillGaps(chatID string, messageIDs []string) error { 1019 messages, err := m.persistence.MessagesByIDs(messageIDs) 1020 if err != nil { 1021 return err 1022 } 1023 1024 chat, ok := m.allChats.Load(chatID) 1025 if !ok { 1026 return errors.New("chat not existing") 1027 } 1028 1029 pubsubTopic, topics, err := m.topicsForChat(chatID) 1030 if err != nil { 1031 return err 1032 } 1033 1034 var lowestFrom, highestTo uint32 1035 1036 for _, message := range messages { 1037 if message.GapParameters == nil { 1038 return errors.New("can't sync non-gap message") 1039 } 1040 1041 if lowestFrom == 0 || lowestFrom > message.GapParameters.From { 1042 lowestFrom = message.GapParameters.From 1043 } 1044 1045 if highestTo < message.GapParameters.To { 1046 highestTo = message.GapParameters.To 1047 } 1048 } 1049 1050 batch := MailserverBatch{ 1051 ChatIDs: []string{chatID}, 1052 To: highestTo, 1053 From: lowestFrom, 1054 PubsubTopic: pubsubTopic, 1055 Topics: topics, 1056 } 1057 1058 if m.config.messengerSignalsHandler != nil { 1059 m.config.messengerSignalsHandler.HistoryRequestStarted(1) 1060 } 1061 1062 ms := m.getActiveMailserver(chat.CommunityID) 1063 err = m.processMailserverBatch(*ms, batch) 1064 if err != nil { 1065 return err 1066 } 1067 1068 if m.config.messengerSignalsHandler != nil { 1069 m.config.messengerSignalsHandler.HistoryRequestCompleted() 1070 } 1071 1072 return m.persistence.DeleteMessages(messageIDs) 1073 } 1074 1075 func (m *Messenger) waitUntilP2PMessagesProcessed() { // nolint: unused 1076 1077 ticker := time.NewTicker(50 * time.Millisecond) 1078 1079 for { //nolint: gosimple 1080 select { 1081 case <-ticker.C: 1082 if !m.transport.ProcessingP2PMessages() { 1083 ticker.Stop() 1084 return 1085 } 1086 } 1087 } 1088 } 1089 1090 func (m *Messenger) LoadFilters(filters []*transport.Filter) ([]*transport.Filter, error) { 1091 return m.transport.LoadFilters(filters) 1092 } 1093 1094 func (m *Messenger) ToggleUseMailservers(value bool) error { 1095 m.mailserverCycle.Lock() 1096 defer m.mailserverCycle.Unlock() 1097 1098 err := m.settings.SetUseMailservers(value) 1099 if err != nil { 1100 return err 1101 } 1102 1103 m.disconnectActiveMailserver(backoffByUserAction) 1104 if value { 1105 m.cycleMailservers() 1106 return nil 1107 } 1108 return nil 1109 } 1110 1111 func (m *Messenger) SetPinnedMailservers(mailservers map[string]string) error { 1112 err := m.settings.SetPinnedMailservers(mailservers) 1113 if err != nil { 1114 return err 1115 } 1116 1117 m.disconnectActiveMailserver(backoffByUserAction) 1118 m.cycleMailservers() 1119 return nil 1120 } 1121 1122 func (m *Messenger) RemoveFilters(filters []*transport.Filter) error { 1123 return m.transport.RemoveFilters(filters) 1124 } 1125 1126 func (m *Messenger) ConnectionChanged(state connection.State) { 1127 m.transport.ConnectionChanged(state) 1128 if !m.connectionState.Offline && state.Offline { 1129 m.sender.StopDatasync() 1130 } 1131 1132 if m.connectionState.Offline && !state.Offline { 1133 err := m.sender.StartDatasync(m.mvdsStatusChangeEvent, m.sendDataSync) 1134 if err != nil { 1135 m.logger.Error("failed to start datasync", zap.Error(err)) 1136 } 1137 } 1138 1139 m.connectionState = state 1140 } 1141 1142 func (m *Messenger) fetchMessages(chatID string, duration time.Duration) (uint32, error) { 1143 from, to := m.calculateMailserverTimeBounds(duration) 1144 1145 chat, ok := m.allChats.Load(chatID) 1146 if !ok { 1147 return 0, ErrChatNotFound 1148 } 1149 1150 ms := m.getActiveMailserver(chat.CommunityID) 1151 _, err := m.performMailserverRequest(ms, func(ms mailservers.Mailserver) (*MessengerResponse, error) { 1152 canSync, err := m.canSyncWithStoreNodes() 1153 if err != nil { 1154 return nil, err 1155 } 1156 if !canSync { 1157 return nil, nil 1158 } 1159 1160 m.logger.Debug("fetching messages", zap.String("chatID", chatID), zap.String("mailserver", ms.Name)) 1161 pubsubTopic, topics, err := m.topicsForChat(chatID) 1162 if err != nil { 1163 return nil, nil 1164 } 1165 1166 batch := MailserverBatch{ 1167 ChatIDs: []string{chatID}, 1168 From: from, 1169 To: to, 1170 PubsubTopic: pubsubTopic, 1171 Topics: topics, 1172 } 1173 if m.config.messengerSignalsHandler != nil { 1174 m.config.messengerSignalsHandler.HistoryRequestStarted(1) 1175 } 1176 1177 err = m.processMailserverBatch(ms, batch) 1178 if err != nil { 1179 return nil, err 1180 } 1181 1182 if m.config.messengerSignalsHandler != nil { 1183 m.config.messengerSignalsHandler.HistoryRequestCompleted() 1184 } 1185 if chat.SyncedFrom == 0 || chat.SyncedFrom > batch.From { 1186 chat.SyncedFrom = batch.From 1187 } 1188 1189 m.logger.Debug("setting sync timestamps", zap.Int64("from", int64(batch.From)), zap.Int64("to", int64(chat.SyncedTo)), zap.String("chatID", chatID)) 1190 1191 err = m.persistence.SetSyncTimestamps(batch.From, chat.SyncedTo, chat.ID) 1192 from = batch.From 1193 return nil, err 1194 }) 1195 if err != nil { 1196 return 0, err 1197 } 1198 1199 return from, nil 1200 }