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 }