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

     1  package transport
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"math"
     7  	"sync"
     8  	"time"
     9  
    10  	"go.uber.org/zap"
    11  
    12  	"github.com/status-im/status-go/eth-node/types"
    13  )
    14  
    15  // EnvelopeState in local tracker
    16  type EnvelopeState int
    17  
    18  const (
    19  	// NotRegistered returned if asked hash wasn't registered in the tracker.
    20  	NotRegistered EnvelopeState = -1
    21  	// EnvelopePosted is set when envelope was added to a local waku queue.
    22  	EnvelopePosted EnvelopeState = iota + 1
    23  	// EnvelopeSent is set when envelope is sent to at least one peer.
    24  	EnvelopeSent
    25  )
    26  
    27  type EnvelopesMonitorConfig struct {
    28  	EnvelopeEventsHandler            EnvelopeEventsHandler
    29  	MaxAttempts                      int
    30  	AwaitOnlyMailServerConfirmations bool
    31  	IsMailserver                     func(types.EnodeID) bool
    32  	Logger                           *zap.Logger
    33  }
    34  
    35  // EnvelopeEventsHandler used for two different event types.
    36  type EnvelopeEventsHandler interface {
    37  	EnvelopeSent([][]byte)
    38  	EnvelopeExpired([][]byte, error)
    39  	MailServerRequestCompleted(types.Hash, types.Hash, []byte, error)
    40  	MailServerRequestExpired(types.Hash)
    41  }
    42  
    43  // NewEnvelopesMonitor returns a pointer to an instance of the EnvelopesMonitor.
    44  func NewEnvelopesMonitor(w types.Waku, config EnvelopesMonitorConfig) *EnvelopesMonitor {
    45  	logger := config.Logger
    46  
    47  	if logger == nil {
    48  		logger = zap.NewNop()
    49  	}
    50  
    51  	var api types.PublicWakuAPI
    52  	if w != nil {
    53  		api = w.PublicWakuAPI()
    54  	}
    55  
    56  	return &EnvelopesMonitor{
    57  		w:                                w,
    58  		api:                              api,
    59  		handler:                          config.EnvelopeEventsHandler,
    60  		awaitOnlyMailServerConfirmations: config.AwaitOnlyMailServerConfirmations,
    61  		maxAttempts:                      config.MaxAttempts,
    62  		isMailserver:                     config.IsMailserver,
    63  		logger:                           logger.With(zap.Namespace("EnvelopesMonitor")),
    64  
    65  		// key is envelope hash (event.Hash)
    66  		envelopes: map[types.Hash]*monitoredEnvelope{},
    67  
    68  		// key is hash of the batch (event.Batch)
    69  		batches: map[types.Hash]map[types.Hash]struct{}{},
    70  
    71  		// key is stringified message identifier
    72  		messageEnvelopeHashes: make(map[string][]types.Hash),
    73  	}
    74  }
    75  
    76  type monitoredEnvelope struct {
    77  	envelopeHashID  types.Hash
    78  	state           EnvelopeState
    79  	attempts        int
    80  	message         *types.NewMessage
    81  	messageIDs      [][]byte
    82  	lastAttemptTime time.Time
    83  }
    84  
    85  // EnvelopesMonitor is responsible for monitoring waku envelopes state.
    86  type EnvelopesMonitor struct {
    87  	w           types.Waku
    88  	api         types.PublicWakuAPI
    89  	handler     EnvelopeEventsHandler
    90  	maxAttempts int
    91  
    92  	mu sync.Mutex
    93  
    94  	envelopes             map[types.Hash]*monitoredEnvelope
    95  	retryQueue            []*monitoredEnvelope
    96  	batches               map[types.Hash]map[types.Hash]struct{}
    97  	messageEnvelopeHashes map[string][]types.Hash
    98  
    99  	awaitOnlyMailServerConfirmations bool
   100  
   101  	wg           sync.WaitGroup
   102  	quit         chan struct{}
   103  	isMailserver func(peer types.EnodeID) bool
   104  
   105  	logger *zap.Logger
   106  }
   107  
   108  // Start processing events.
   109  func (m *EnvelopesMonitor) Start() {
   110  	m.quit = make(chan struct{})
   111  	m.wg.Add(2)
   112  	go func() {
   113  		m.handleEnvelopeEvents()
   114  		m.wg.Done()
   115  	}()
   116  	go func() {
   117  		defer m.wg.Done()
   118  		m.retryLoop()
   119  	}()
   120  }
   121  
   122  // Stop process events.
   123  func (m *EnvelopesMonitor) Stop() {
   124  	close(m.quit)
   125  	m.wg.Wait()
   126  }
   127  
   128  // Add hashes to a tracker.
   129  // Identifiers may be backed by multiple envelopes. It happens when message is split in segmentation layer.
   130  func (m *EnvelopesMonitor) Add(messageIDs [][]byte, envelopeHashes []types.Hash, messages []*types.NewMessage) error {
   131  	if len(envelopeHashes) != len(messages) {
   132  		return errors.New("hashes don't match messages")
   133  	}
   134  
   135  	m.mu.Lock()
   136  	defer m.mu.Unlock()
   137  
   138  	for _, messageID := range messageIDs {
   139  		m.messageEnvelopeHashes[types.HexBytes(messageID).String()] = envelopeHashes
   140  	}
   141  
   142  	for i, envelopeHash := range envelopeHashes {
   143  		if _, ok := m.envelopes[envelopeHash]; !ok {
   144  			m.envelopes[envelopeHash] = &monitoredEnvelope{
   145  				envelopeHashID:  envelopeHash,
   146  				state:           EnvelopePosted,
   147  				attempts:        1,
   148  				lastAttemptTime: time.Now(),
   149  				message:         messages[i],
   150  				messageIDs:      messageIDs,
   151  			}
   152  		}
   153  	}
   154  
   155  	m.processMessageIDs(messageIDs)
   156  
   157  	return nil
   158  }
   159  
   160  func (m *EnvelopesMonitor) GetState(hash types.Hash) EnvelopeState {
   161  	m.mu.Lock()
   162  	defer m.mu.Unlock()
   163  	envelope, exist := m.envelopes[hash]
   164  	if !exist {
   165  		return NotRegistered
   166  	}
   167  	return envelope.state
   168  }
   169  
   170  // handleEnvelopeEvents processes waku envelope events
   171  func (m *EnvelopesMonitor) handleEnvelopeEvents() {
   172  	events := make(chan types.EnvelopeEvent, 100) // must be buffered to prevent blocking waku
   173  	sub := m.w.SubscribeEnvelopeEvents(events)
   174  	defer func() {
   175  		sub.Unsubscribe()
   176  	}()
   177  	for {
   178  		select {
   179  		case <-m.quit:
   180  			return
   181  		case event := <-events:
   182  			m.handleEvent(event)
   183  		}
   184  	}
   185  }
   186  
   187  // handleEvent based on type of the event either triggers
   188  // confirmation handler or removes hash from tracker
   189  func (m *EnvelopesMonitor) handleEvent(event types.EnvelopeEvent) {
   190  	handlers := map[types.EventType]func(types.EnvelopeEvent){
   191  		types.EventEnvelopeSent:      m.handleEventEnvelopeSent,
   192  		types.EventEnvelopeExpired:   m.handleEventEnvelopeExpired,
   193  		types.EventBatchAcknowledged: m.handleAcknowledgedBatch,
   194  		types.EventEnvelopeReceived:  m.handleEventEnvelopeReceived,
   195  	}
   196  	if handler, ok := handlers[event.Event]; ok {
   197  		handler(event)
   198  	}
   199  }
   200  
   201  func (m *EnvelopesMonitor) handleEventEnvelopeSent(event types.EnvelopeEvent) {
   202  	// Mailserver confirmations for WakuV2 are disabled
   203  	if (m.w == nil || m.w.Version() < 2) && m.awaitOnlyMailServerConfirmations {
   204  		if !m.isMailserver(event.Peer) {
   205  			return
   206  		}
   207  	}
   208  
   209  	m.mu.Lock()
   210  	defer m.mu.Unlock()
   211  
   212  	confirmationExpected := event.Batch != (types.Hash{})
   213  
   214  	envelope, ok := m.envelopes[event.Hash]
   215  
   216  	// If confirmations are not expected, we keep track of the envelope
   217  	// being sent
   218  	if !ok && !confirmationExpected {
   219  		m.envelopes[event.Hash] = &monitoredEnvelope{envelopeHashID: event.Hash, state: EnvelopeSent}
   220  		return
   221  	}
   222  
   223  	// if message was already confirmed - skip it
   224  	if envelope.state == EnvelopeSent {
   225  		return
   226  	}
   227  	m.logger.Debug("envelope is sent", zap.String("hash", event.Hash.String()), zap.String("peer", event.Peer.String()))
   228  	if confirmationExpected {
   229  		if _, ok := m.batches[event.Batch]; !ok {
   230  			m.batches[event.Batch] = map[types.Hash]struct{}{}
   231  		}
   232  		m.batches[event.Batch][event.Hash] = struct{}{}
   233  		m.logger.Debug("waiting for a confirmation", zap.String("batch", event.Batch.String()))
   234  	} else {
   235  		m.logger.Debug("confirmation not expected, marking as sent")
   236  		envelope.state = EnvelopeSent
   237  		m.processMessageIDs(envelope.messageIDs)
   238  	}
   239  }
   240  
   241  func (m *EnvelopesMonitor) handleAcknowledgedBatch(event types.EnvelopeEvent) {
   242  
   243  	if m.awaitOnlyMailServerConfirmations && !m.isMailserver(event.Peer) {
   244  		return
   245  	}
   246  
   247  	m.mu.Lock()
   248  	defer m.mu.Unlock()
   249  
   250  	envelopes, ok := m.batches[event.Batch]
   251  	if !ok {
   252  		m.logger.Debug("batch is not found", zap.String("batch", event.Batch.String()))
   253  	}
   254  	m.logger.Debug("received a confirmation", zap.String("batch", event.Batch.String()), zap.String("peer", event.Peer.String()))
   255  	envelopeErrors, ok := event.Data.([]types.EnvelopeError)
   256  	if event.Data != nil && !ok {
   257  		m.logger.Error("received unexpected data in the the confirmation event", zap.Any("data", event.Data))
   258  	}
   259  	failedEnvelopes := map[types.Hash]struct{}{}
   260  	for i := range envelopeErrors {
   261  		envelopeError := envelopeErrors[i]
   262  		_, exist := m.envelopes[envelopeError.Hash]
   263  		if exist {
   264  			m.logger.Warn("envelope that was posted by us is discarded", zap.String("hash", envelopeError.Hash.String()), zap.String("peer", event.Peer.String()), zap.String("error", envelopeError.Description))
   265  			var err error
   266  			switch envelopeError.Code {
   267  			case types.EnvelopeTimeNotSynced:
   268  				err = errors.New("envelope wasn't delivered due to time sync issues")
   269  			}
   270  			m.handleEnvelopeFailure(envelopeError.Hash, err)
   271  		}
   272  		failedEnvelopes[envelopeError.Hash] = struct{}{}
   273  	}
   274  
   275  	for hash := range envelopes {
   276  		if _, exist := failedEnvelopes[hash]; exist {
   277  			continue
   278  		}
   279  		envelope, ok := m.envelopes[hash]
   280  		if !ok || envelope.state == EnvelopeSent {
   281  			continue
   282  		}
   283  		envelope.state = EnvelopeSent
   284  		m.processMessageIDs(envelope.messageIDs)
   285  	}
   286  	delete(m.batches, event.Batch)
   287  }
   288  
   289  func (m *EnvelopesMonitor) handleEventEnvelopeExpired(event types.EnvelopeEvent) {
   290  	m.mu.Lock()
   291  	defer m.mu.Unlock()
   292  	m.handleEnvelopeFailure(event.Hash, errors.New("envelope expired due to connectivity issues"))
   293  }
   294  
   295  // handleEnvelopeFailure is a common code path for processing envelopes failures. not thread safe, lock
   296  // must be used on a higher level.
   297  func (m *EnvelopesMonitor) handleEnvelopeFailure(hash types.Hash, err error) {
   298  	if envelope, ok := m.envelopes[hash]; ok {
   299  		m.clearMessageState(hash)
   300  		if envelope.state == EnvelopeSent {
   301  			return
   302  		}
   303  		if envelope.attempts < m.maxAttempts {
   304  			m.retryQueue = append(m.retryQueue, envelope)
   305  		} else {
   306  			m.logger.Debug("envelope expired", zap.String("hash", hash.String()))
   307  			m.removeFromRetryQueue(hash)
   308  			if m.handler != nil {
   309  				m.handler.EnvelopeExpired(envelope.messageIDs, err)
   310  			}
   311  		}
   312  	}
   313  }
   314  
   315  func backoffDuration(attempts int) time.Duration {
   316  	baseDelay := 1 * time.Second
   317  	maxDelay := 30 * time.Second
   318  	backoff := baseDelay * time.Duration(math.Pow(2, float64(attempts)))
   319  	if backoff > maxDelay {
   320  		backoff = maxDelay
   321  	}
   322  	return backoff
   323  }
   324  
   325  // retryLoop handles the retry logic to send envelope in a loop
   326  func (m *EnvelopesMonitor) retryLoop() {
   327  	ticker := time.NewTicker(500 * time.Millisecond) // Timer, triggers every 500 milliseconds
   328  	defer ticker.Stop()
   329  
   330  	for {
   331  		select {
   332  		case <-m.quit:
   333  			return
   334  		case <-ticker.C:
   335  			m.retryOnce()
   336  		}
   337  	}
   338  }
   339  
   340  // retryOnce retries once
   341  func (m *EnvelopesMonitor) retryOnce() {
   342  	m.mu.Lock()
   343  	defer m.mu.Unlock()
   344  
   345  	for _, envelope := range m.retryQueue {
   346  		if envelope.attempts < m.maxAttempts {
   347  			elapsed := time.Since(envelope.lastAttemptTime)
   348  			if elapsed < backoffDuration(envelope.attempts) {
   349  				continue
   350  			}
   351  
   352  			m.logger.Debug("retrying to send a message", zap.String("hash", envelope.envelopeHashID.String()), zap.Int("attempt", envelope.attempts+1))
   353  			hex, err := m.api.Post(context.TODO(), *envelope.message)
   354  			if err != nil {
   355  				m.logger.Error("failed to retry sending message", zap.String("hash", envelope.envelopeHashID.String()), zap.Int("attempt", envelope.attempts+1), zap.Error(err))
   356  				if m.handler != nil {
   357  					m.handler.EnvelopeExpired(envelope.messageIDs, err)
   358  				}
   359  			} else {
   360  				m.removeFromRetryQueue(envelope.envelopeHashID)
   361  				envelope.envelopeHashID = types.BytesToHash(hex)
   362  			}
   363  			envelope.state = EnvelopePosted
   364  			envelope.attempts++
   365  			envelope.lastAttemptTime = time.Now()
   366  			m.envelopes[envelope.envelopeHashID] = envelope
   367  		}
   368  	}
   369  }
   370  
   371  // removeFromRetryQueue removes the specified envelope from the retry queue
   372  func (m *EnvelopesMonitor) removeFromRetryQueue(envelopeID types.Hash) {
   373  	var newRetryQueue []*monitoredEnvelope
   374  	for _, envelope := range m.retryQueue {
   375  		if envelope.envelopeHashID != envelopeID {
   376  			newRetryQueue = append(newRetryQueue, envelope)
   377  		}
   378  	}
   379  	m.retryQueue = newRetryQueue
   380  }
   381  
   382  func (m *EnvelopesMonitor) handleEventEnvelopeReceived(event types.EnvelopeEvent) {
   383  	if m.awaitOnlyMailServerConfirmations && !m.isMailserver(event.Peer) {
   384  		return
   385  	}
   386  	m.mu.Lock()
   387  	defer m.mu.Unlock()
   388  	envelope, ok := m.envelopes[event.Hash]
   389  	if !ok || envelope.state != EnvelopePosted {
   390  		return
   391  	}
   392  	m.logger.Debug("expected envelope received", zap.String("hash", event.Hash.String()), zap.String("peer", event.Peer.String()))
   393  	envelope.state = EnvelopeSent
   394  	m.processMessageIDs(envelope.messageIDs)
   395  }
   396  
   397  func (m *EnvelopesMonitor) processMessageIDs(messageIDs [][]byte) {
   398  	sentMessageIDs := make([][]byte, 0, len(messageIDs))
   399  
   400  	for _, messageID := range messageIDs {
   401  		hashes, ok := m.messageEnvelopeHashes[types.HexBytes(messageID).String()]
   402  		if !ok {
   403  			continue
   404  		}
   405  
   406  		sent := true
   407  		// Consider message as sent if all corresponding envelopes are in EnvelopeSent state
   408  		for _, hash := range hashes {
   409  			envelope, ok := m.envelopes[hash]
   410  			if !ok || envelope.state != EnvelopeSent {
   411  				sent = false
   412  				break
   413  			}
   414  		}
   415  		if sent {
   416  			sentMessageIDs = append(sentMessageIDs, messageID)
   417  		}
   418  	}
   419  
   420  	if len(sentMessageIDs) > 0 && m.handler != nil {
   421  		m.handler.EnvelopeSent(sentMessageIDs)
   422  	}
   423  }
   424  
   425  // clearMessageState removes all message and envelope state.
   426  // not thread-safe, should be protected on a higher level.
   427  func (m *EnvelopesMonitor) clearMessageState(envelopeID types.Hash) {
   428  	envelope, ok := m.envelopes[envelopeID]
   429  	if !ok {
   430  		return
   431  	}
   432  	delete(m.envelopes, envelopeID)
   433  	for _, messageID := range envelope.messageIDs {
   434  		delete(m.messageEnvelopeHashes, types.HexBytes(messageID).String())
   435  	}
   436  }