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  }