github.com/status-im/status-go@v1.1.0/protocol/transport/transport.go (about) 1 package transport 2 3 import ( 4 "context" 5 "crypto/ecdsa" 6 "database/sql" 7 "encoding/hex" 8 "sync" 9 "time" 10 11 "github.com/google/uuid" 12 "github.com/libp2p/go-libp2p/core/peer" 13 "github.com/multiformats/go-multiaddr" 14 "github.com/pkg/errors" 15 "go.uber.org/zap" 16 "golang.org/x/exp/maps" 17 18 "github.com/ethereum/go-ethereum/common" 19 "github.com/ethereum/go-ethereum/p2p/enode" 20 "github.com/status-im/status-go/connection" 21 "github.com/status-im/status-go/eth-node/crypto" 22 "github.com/status-im/status-go/eth-node/types" 23 ) 24 25 var ( 26 // ErrNoMailservers returned if there is no configured mailservers that can be used. 27 ErrNoMailservers = errors.New("no configured mailservers") 28 ) 29 30 type transportKeysManager struct { 31 waku types.Waku 32 33 // Identity of the current user. 34 privateKey *ecdsa.PrivateKey 35 36 passToSymKeyMutex sync.RWMutex 37 passToSymKeyCache map[string]string 38 } 39 40 func (m *transportKeysManager) AddOrGetKeyPair(priv *ecdsa.PrivateKey) (string, error) { 41 // caching is handled in waku 42 return m.waku.AddKeyPair(priv) 43 } 44 45 func (m *transportKeysManager) AddOrGetSymKeyFromPassword(password string) (string, error) { 46 m.passToSymKeyMutex.Lock() 47 defer m.passToSymKeyMutex.Unlock() 48 49 if val, ok := m.passToSymKeyCache[password]; ok { 50 return val, nil 51 } 52 53 id, err := m.waku.AddSymKeyFromPassword(password) 54 if err != nil { 55 return id, err 56 } 57 58 m.passToSymKeyCache[password] = id 59 60 return id, nil 61 } 62 63 func (m *transportKeysManager) RawSymKey(id string) ([]byte, error) { 64 return m.waku.GetSymKey(id) 65 } 66 67 type Option func(*Transport) error 68 69 // Transport is a transport based on Whisper service. 70 type Transport struct { 71 waku types.Waku 72 api types.PublicWakuAPI // only PublicWakuAPI implements logic to send messages 73 keysManager *transportKeysManager 74 filters *FiltersManager 75 logger *zap.Logger 76 cache *ProcessedMessageIDsCache 77 78 mailservers []string 79 envelopesMonitor *EnvelopesMonitor 80 quit chan struct{} 81 } 82 83 // NewTransport returns a new Transport. 84 // TODO: leaving a chat should verify that for a given public key 85 // 86 // there are no other chats. It may happen that we leave a private chat 87 // but still have a public chat for a given public key. 88 func NewTransport( 89 waku types.Waku, 90 privateKey *ecdsa.PrivateKey, 91 db *sql.DB, 92 sqlitePersistenceTableName string, 93 mailservers []string, 94 envelopesMonitorConfig *EnvelopesMonitorConfig, 95 logger *zap.Logger, 96 opts ...Option, 97 ) (*Transport, error) { 98 filtersManager, err := NewFiltersManager(newSQLitePersistence(db, sqlitePersistenceTableName), waku, privateKey, logger) 99 if err != nil { 100 return nil, err 101 } 102 103 var envelopesMonitor *EnvelopesMonitor 104 if envelopesMonitorConfig != nil { 105 envelopesMonitor = NewEnvelopesMonitor(waku, *envelopesMonitorConfig) 106 envelopesMonitor.Start() 107 } 108 109 var api types.PublicWhisperAPI 110 if waku != nil { 111 api = waku.PublicWakuAPI() 112 } 113 t := &Transport{ 114 waku: waku, 115 api: api, 116 cache: NewProcessedMessageIDsCache(db), 117 envelopesMonitor: envelopesMonitor, 118 quit: make(chan struct{}), 119 keysManager: &transportKeysManager{ 120 waku: waku, 121 privateKey: privateKey, 122 passToSymKeyCache: make(map[string]string), 123 }, 124 filters: filtersManager, 125 mailservers: mailservers, 126 logger: logger.With(zap.Namespace("Transport")), 127 } 128 129 for _, opt := range opts { 130 if err := opt(t); err != nil { 131 return nil, err 132 } 133 } 134 135 t.cleanFiltersLoop() 136 137 return t, nil 138 } 139 140 func (t *Transport) InitFilters(chatIDs []FiltersToInitialize, publicKeys []*ecdsa.PublicKey) ([]*Filter, error) { 141 return t.filters.Init(chatIDs, publicKeys) 142 } 143 144 func (t *Transport) InitPublicFilters(filtersToInit []FiltersToInitialize) ([]*Filter, error) { 145 return t.filters.InitPublicFilters(filtersToInit) 146 } 147 148 func (t *Transport) Filters() []*Filter { 149 return t.filters.Filters() 150 } 151 152 func (t *Transport) FilterByChatID(chatID string) *Filter { 153 return t.filters.FilterByChatID(chatID) 154 } 155 156 func (t *Transport) FilterByTopic(topic []byte) *Filter { 157 return t.filters.FilterByTopic(topic) 158 } 159 160 func (t *Transport) FiltersByIdentities(identities []string) []*Filter { 161 return t.filters.FiltersByIdentities(identities) 162 } 163 164 func (t *Transport) LoadFilters(filters []*Filter) ([]*Filter, error) { 165 return t.filters.InitWithFilters(filters) 166 } 167 168 func (t *Transport) InitCommunityFilters(communityFiltersToInitialize []CommunityFilterToInitialize) ([]*Filter, error) { 169 return t.filters.InitCommunityFilters(communityFiltersToInitialize) 170 } 171 172 func (t *Transport) RemoveFilters(filters []*Filter) error { 173 return t.filters.Remove(context.Background(), filters...) 174 } 175 176 func (t *Transport) RemoveFilterByChatID(chatID string) (*Filter, error) { 177 return t.filters.RemoveFilterByChatID(chatID) 178 } 179 180 func (t *Transport) ResetFilters(ctx context.Context) error { 181 return t.filters.Reset(ctx) 182 } 183 184 func (t *Transport) ProcessNegotiatedSecret(secret types.NegotiatedSecret) (*Filter, error) { 185 filter, err := t.filters.LoadNegotiated(secret) 186 if err != nil { 187 return nil, err 188 } 189 return filter, nil 190 } 191 192 func (t *Transport) JoinPublic(chatID string) (*Filter, error) { 193 return t.filters.LoadPublic(chatID, "") 194 } 195 196 func (t *Transport) LeavePublic(chatID string) error { 197 chat := t.filters.Filter(chatID) 198 if chat != nil { 199 return nil 200 } 201 return t.filters.Remove(context.Background(), chat) 202 } 203 204 func (t *Transport) JoinPrivate(publicKey *ecdsa.PublicKey) (*Filter, error) { 205 return t.filters.LoadContactCode(publicKey) 206 } 207 208 func (t *Transport) JoinGroup(publicKeys []*ecdsa.PublicKey) ([]*Filter, error) { 209 var filters []*Filter 210 for _, pk := range publicKeys { 211 f, err := t.filters.LoadContactCode(pk) 212 if err != nil { 213 return nil, err 214 } 215 filters = append(filters, f) 216 217 } 218 return filters, nil 219 } 220 221 func (t *Transport) GetStats() types.StatsSummary { 222 return t.waku.GetStats() 223 } 224 225 func (t *Transport) RetrieveRawAll() (map[Filter][]*types.Message, error) { 226 result := make(map[Filter][]*types.Message) 227 logger := t.logger.With(zap.String("site", "retrieveRawAll")) 228 229 for _, filter := range t.filters.Filters() { 230 msgs, err := t.api.GetFilterMessages(filter.FilterID) 231 if err != nil { 232 logger.Warn("failed to fetch messages", zap.Error(err)) 233 continue 234 } 235 // Don't pull from filters we don't listen to 236 if !filter.Listen { 237 for _, msg := range msgs { 238 t.waku.MarkP2PMessageAsProcessed(common.BytesToHash(msg.Hash)) 239 } 240 continue 241 } 242 243 if len(msgs) == 0 { 244 continue 245 } 246 247 ids := make([]string, len(msgs)) 248 for i := range msgs { 249 id := types.EncodeHex(msgs[i].Hash) 250 ids[i] = id 251 } 252 253 hits, err := t.cache.Hits(ids) 254 if err != nil { 255 logger.Error("failed to check messages exists", zap.Error(err)) 256 return nil, err 257 } 258 259 for i := range msgs { 260 // Exclude anything that is a cache hit 261 if !hits[types.EncodeHex(msgs[i].Hash)] { 262 result[*filter] = append(result[*filter], msgs[i]) 263 logger.Debug("message not cached", zap.String("hash", types.EncodeHex(msgs[i].Hash))) 264 } else { 265 logger.Debug("message cached", zap.String("hash", types.EncodeHex(msgs[i].Hash))) 266 t.waku.MarkP2PMessageAsProcessed(common.BytesToHash(msgs[i].Hash)) 267 } 268 } 269 270 } 271 272 return result, nil 273 } 274 275 // SendPublic sends a new message using the Whisper service. 276 // For public filters, chat name is used as an ID as well as 277 // a topic. 278 func (t *Transport) SendPublic(ctx context.Context, newMessage *types.NewMessage, chatName string) ([]byte, error) { 279 if err := t.addSig(newMessage); err != nil { 280 return nil, err 281 } 282 283 filter, err := t.filters.LoadPublic(chatName, newMessage.PubsubTopic) 284 if err != nil { 285 return nil, err 286 } 287 288 newMessage.SymKeyID = filter.SymKeyID 289 newMessage.Topic = filter.ContentTopic 290 newMessage.PubsubTopic = filter.PubsubTopic 291 292 return t.api.Post(ctx, *newMessage) 293 } 294 295 func (t *Transport) SendPrivateWithSharedSecret(ctx context.Context, newMessage *types.NewMessage, publicKey *ecdsa.PublicKey, secret []byte) ([]byte, error) { 296 if err := t.addSig(newMessage); err != nil { 297 return nil, err 298 } 299 300 filter, err := t.filters.LoadNegotiated(types.NegotiatedSecret{ 301 PublicKey: publicKey, 302 Key: secret, 303 }) 304 if err != nil { 305 return nil, err 306 } 307 308 newMessage.SymKeyID = filter.SymKeyID 309 newMessage.Topic = filter.ContentTopic 310 newMessage.PubsubTopic = filter.PubsubTopic 311 newMessage.PublicKey = nil 312 313 return t.api.Post(ctx, *newMessage) 314 } 315 316 func (t *Transport) SendPrivateWithPartitioned(ctx context.Context, newMessage *types.NewMessage, publicKey *ecdsa.PublicKey) ([]byte, error) { 317 if err := t.addSig(newMessage); err != nil { 318 return nil, err 319 } 320 321 filter, err := t.filters.LoadPartitioned(publicKey, t.keysManager.privateKey, false) 322 if err != nil { 323 return nil, err 324 } 325 326 newMessage.PubsubTopic = filter.PubsubTopic 327 newMessage.Topic = filter.ContentTopic 328 newMessage.PublicKey = crypto.FromECDSAPub(publicKey) 329 330 return t.api.Post(ctx, *newMessage) 331 } 332 333 func (t *Transport) SendPrivateOnPersonalTopic(ctx context.Context, newMessage *types.NewMessage, publicKey *ecdsa.PublicKey) ([]byte, error) { 334 if err := t.addSig(newMessage); err != nil { 335 return nil, err 336 } 337 338 filter, err := t.filters.LoadPersonal(publicKey, t.keysManager.privateKey, false) 339 if err != nil { 340 return nil, err 341 } 342 343 newMessage.PubsubTopic = filter.PubsubTopic 344 newMessage.Topic = filter.ContentTopic 345 newMessage.PublicKey = crypto.FromECDSAPub(publicKey) 346 347 return t.api.Post(ctx, *newMessage) 348 } 349 350 func (t *Transport) PersonalTopicFilter() *Filter { 351 return t.filters.PersonalTopicFilter() 352 } 353 354 func (t *Transport) LoadKeyFilters(key *ecdsa.PrivateKey) (*Filter, error) { 355 return t.filters.LoadEphemeral(&key.PublicKey, key, true) 356 } 357 358 func (t *Transport) SendCommunityMessage(ctx context.Context, newMessage *types.NewMessage, publicKey *ecdsa.PublicKey) ([]byte, error) { 359 if err := t.addSig(newMessage); err != nil { 360 return nil, err 361 } 362 363 // We load the filter to make sure we can post on it 364 filter, err := t.filters.LoadPublic(PubkeyToHex(publicKey)[2:], newMessage.PubsubTopic) 365 if err != nil { 366 return nil, err 367 } 368 369 newMessage.PubsubTopic = filter.PubsubTopic 370 newMessage.Topic = filter.ContentTopic 371 newMessage.PublicKey = crypto.FromECDSAPub(publicKey) 372 373 return t.api.Post(ctx, *newMessage) 374 } 375 376 func (t *Transport) cleanFilters() error { 377 return t.filters.RemoveNoListenFilters() 378 } 379 380 func (t *Transport) addSig(newMessage *types.NewMessage) error { 381 sigID, err := t.keysManager.AddOrGetKeyPair(t.keysManager.privateKey) 382 if err != nil { 383 return err 384 } 385 newMessage.SigID = sigID 386 return nil 387 } 388 389 func (t *Transport) Track(identifier []byte, hashes [][]byte, newMessages []*types.NewMessage) { 390 t.TrackMany([][]byte{identifier}, hashes, newMessages) 391 } 392 393 func (t *Transport) TrackMany(identifiers [][]byte, hashes [][]byte, newMessages []*types.NewMessage) { 394 if t.envelopesMonitor == nil { 395 return 396 } 397 398 envelopeHashes := make([]types.Hash, len(hashes)) 399 for i, hash := range hashes { 400 envelopeHashes[i] = types.BytesToHash(hash) 401 } 402 403 err := t.envelopesMonitor.Add(identifiers, envelopeHashes, newMessages) 404 if err != nil { 405 t.logger.Error("failed to track messages", zap.Error(err)) 406 } 407 } 408 409 // GetCurrentTime returns the current unix timestamp in milliseconds 410 func (t *Transport) GetCurrentTime() uint64 { 411 return uint64(t.waku.GetCurrentTime().UnixNano() / int64(time.Millisecond)) 412 } 413 414 func (t *Transport) MaxMessageSize() uint32 { 415 return t.waku.MaxMessageSize() 416 } 417 418 func (t *Transport) Stop() error { 419 close(t.quit) 420 if t.envelopesMonitor != nil { 421 t.envelopesMonitor.Stop() 422 } 423 return nil 424 } 425 426 // cleanFiltersLoop cleans up the topic we create for the only purpose 427 // of sending messages. 428 // Whenever we send a message we also need to listen to that particular topic 429 // but in case of asymettric topics, we are not interested in listening to them. 430 // We therefore periodically clean them up so we don't receive unnecessary data. 431 432 func (t *Transport) cleanFiltersLoop() { 433 434 ticker := time.NewTicker(5 * time.Minute) 435 go func() { 436 for { 437 select { 438 case <-t.quit: 439 ticker.Stop() 440 return 441 case <-ticker.C: 442 err := t.cleanFilters() 443 if err != nil { 444 t.logger.Error("failed to clean up topics", zap.Error(err)) 445 } 446 } 447 } 448 }() 449 } 450 451 func (t *Transport) WakuVersion() uint { 452 return t.waku.Version() 453 } 454 455 func (t *Transport) PeerCount() int { 456 return t.waku.PeerCount() 457 } 458 459 func (t *Transport) Peers() types.PeerStats { 460 return t.waku.Peers() 461 } 462 463 func (t *Transport) createMessagesRequest( 464 ctx context.Context, 465 peerID peer.ID, 466 from, to uint32, 467 previousStoreCursor types.StoreRequestCursor, 468 pubsubTopic string, 469 contentTopics []types.TopicType, 470 limit uint32, 471 waitForResponse bool, 472 processEnvelopes bool, 473 ) (storeCursor types.StoreRequestCursor, envelopesCount int, err error) { 474 r := createMessagesRequest(from, to, nil, previousStoreCursor, pubsubTopic, contentTopics, limit) 475 476 if waitForResponse { 477 resultCh := make(chan struct { 478 storeCursor types.StoreRequestCursor 479 envelopesCount int 480 err error 481 }) 482 483 go func() { 484 storeCursor, envelopesCount, err = t.waku.RequestStoreMessages(ctx, peerID, r, processEnvelopes) 485 resultCh <- struct { 486 storeCursor types.StoreRequestCursor 487 envelopesCount int 488 err error 489 }{storeCursor, envelopesCount, err} 490 }() 491 492 select { 493 case result := <-resultCh: 494 return result.storeCursor, result.envelopesCount, result.err 495 case <-ctx.Done(): 496 return nil, 0, ctx.Err() 497 } 498 } else { 499 go func() { 500 _, _, err = t.waku.RequestStoreMessages(ctx, peerID, r, false) 501 if err != nil { 502 t.logger.Error("failed to request store messages", zap.Error(err)) 503 } 504 }() 505 } 506 507 return 508 } 509 510 func (t *Transport) SendMessagesRequestForTopics( 511 ctx context.Context, 512 peerID peer.ID, 513 from, to uint32, 514 prevCursor types.StoreRequestCursor, 515 pubsubTopic string, 516 contentTopics []types.TopicType, 517 limit uint32, 518 waitForResponse bool, 519 processEnvelopes bool, 520 ) (cursor types.StoreRequestCursor, envelopesCount int, err error) { 521 return t.createMessagesRequest(ctx, peerID, from, to, prevCursor, pubsubTopic, contentTopics, limit, waitForResponse, processEnvelopes) 522 } 523 524 func createMessagesRequest(from, to uint32, cursor []byte, storeCursor types.StoreRequestCursor, pubsubTopic string, topics []types.TopicType, limit uint32) types.MessagesRequest { 525 aUUID := uuid.New() 526 // uuid is 16 bytes, converted to hex it's 32 bytes as expected by types.MessagesRequest 527 id := []byte(hex.EncodeToString(aUUID[:])) 528 var topicBytes [][]byte 529 for idx := range topics { 530 topicBytes = append(topicBytes, topics[idx][:]) 531 } 532 return types.MessagesRequest{ 533 ID: id, 534 From: from, 535 To: to, 536 Limit: limit, 537 Cursor: cursor, 538 PubsubTopic: pubsubTopic, 539 ContentTopics: topicBytes, 540 StoreCursor: storeCursor, 541 } 542 } 543 544 // ConfirmMessagesProcessed marks the messages as processed in the cache so 545 // they won't be passed to the next layer anymore 546 func (t *Transport) ConfirmMessagesProcessed(ids []string, timestamp uint64) error { 547 t.logger.Debug("confirming message processed", zap.Any("ids", ids), zap.Any("timestamp", timestamp)) 548 return t.cache.Add(ids, timestamp) 549 } 550 551 // CleanMessagesProcessed clears the messages that are older than timestamp 552 func (t *Transport) CleanMessagesProcessed(timestamp uint64) error { 553 return t.cache.Clean(timestamp) 554 } 555 556 func (t *Transport) SetEnvelopeEventsHandler(handler EnvelopeEventsHandler) error { 557 if t.envelopesMonitor == nil { 558 return errors.New("Current transport has no envelopes monitor") 559 } 560 t.envelopesMonitor.handler = handler 561 return nil 562 } 563 564 func (t *Transport) ClearProcessedMessageIDsCache() error { 565 t.logger.Debug("clearing processed messages cache") 566 t.waku.ClearEnvelopesCache() 567 return t.cache.Clear() 568 } 569 570 func (t *Transport) BloomFilter() []byte { 571 return t.api.BloomFilter() 572 } 573 574 func PubkeyToHex(key *ecdsa.PublicKey) string { 575 return types.EncodeHex(crypto.FromECDSAPub(key)) 576 } 577 578 func (t *Transport) StartDiscV5() error { 579 return t.waku.StartDiscV5() 580 } 581 582 func (t *Transport) StopDiscV5() error { 583 return t.waku.StopDiscV5() 584 } 585 586 func (t *Transport) ListenAddresses() ([]multiaddr.Multiaddr, error) { 587 return t.waku.ListenAddresses() 588 } 589 590 func (t *Transport) RelayPeersByTopic(topic string) (*types.PeerList, error) { 591 return t.waku.RelayPeersByTopic(topic) 592 } 593 594 func (t *Transport) ENR() (*enode.Node, error) { 595 return t.waku.ENR() 596 } 597 598 func (t *Transport) AddStorePeer(address multiaddr.Multiaddr) (peer.ID, error) { 599 return t.waku.AddStorePeer(address) 600 } 601 602 func (t *Transport) AddRelayPeer(address multiaddr.Multiaddr) (peer.ID, error) { 603 return t.waku.AddRelayPeer(address) 604 } 605 606 func (t *Transport) DialPeer(address multiaddr.Multiaddr) error { 607 return t.waku.DialPeer(address) 608 } 609 610 func (t *Transport) DialPeerByID(peerID peer.ID) error { 611 return t.waku.DialPeerByID(peerID) 612 } 613 614 func (t *Transport) DropPeer(peerID peer.ID) error { 615 return t.waku.DropPeer(peerID) 616 } 617 618 func (t *Transport) ProcessingP2PMessages() bool { 619 return t.waku.ProcessingP2PMessages() 620 } 621 622 func (t *Transport) MarkP2PMessageAsProcessed(hash common.Hash) { 623 t.waku.MarkP2PMessageAsProcessed(hash) 624 } 625 626 func (t *Transport) SubscribeToConnStatusChanges() (*types.ConnStatusSubscription, error) { 627 return t.waku.SubscribeToConnStatusChanges() 628 } 629 630 func (t *Transport) ConnectionChanged(state connection.State) { 631 t.waku.ConnectionChanged(state) 632 } 633 634 func (t *Transport) PingPeer(ctx context.Context, peerID peer.ID) (time.Duration, error) { 635 return t.waku.PingPeer(ctx, peerID) 636 } 637 638 // Subscribe to a pubsub topic, passing an optional public key if the pubsub topic is protected 639 func (t *Transport) SubscribeToPubsubTopic(topic string, optPublicKey *ecdsa.PublicKey) error { 640 if t.waku.Version() == 2 { 641 return t.waku.SubscribeToPubsubTopic(topic, optPublicKey) 642 } 643 return nil 644 } 645 646 // Unsubscribe from a pubsub topic 647 func (t *Transport) UnsubscribeFromPubsubTopic(topic string) error { 648 if t.waku.Version() == 2 { 649 return t.waku.UnsubscribeFromPubsubTopic(topic) 650 } 651 return nil 652 } 653 654 func (t *Transport) StorePubsubTopicKey(topic string, privKey *ecdsa.PrivateKey) error { 655 return t.waku.StorePubsubTopicKey(topic, privKey) 656 } 657 658 func (t *Transport) RetrievePubsubTopicKey(topic string) (*ecdsa.PrivateKey, error) { 659 return t.waku.RetrievePubsubTopicKey(topic) 660 } 661 662 func (t *Transport) RemovePubsubTopicKey(topic string) error { 663 if t.waku.Version() == 2 { 664 return t.waku.RemovePubsubTopicKey(topic) 665 } 666 return nil 667 } 668 669 func (t *Transport) ConfirmMessageDelivered(messageID string) { 670 if t.envelopesMonitor == nil { 671 return 672 } 673 hashes, ok := t.envelopesMonitor.messageEnvelopeHashes[messageID] 674 if !ok { 675 return 676 } 677 commHashes := make([]common.Hash, len(hashes)) 678 for i, h := range hashes { 679 commHashes[i] = common.BytesToHash(h[:]) 680 } 681 t.waku.ConfirmMessageDelivered(commHashes) 682 } 683 684 func (t *Transport) SetStorePeerID(peerID peer.ID) { 685 t.waku.SetStorePeerID(peerID) 686 } 687 688 func (t *Transport) SetCriteriaForMissingMessageVerification(peerID peer.ID, filters []*Filter) { 689 if t.waku.Version() != 2 { 690 return 691 } 692 693 topicMap := make(map[string]map[types.TopicType]struct{}) 694 for _, f := range filters { 695 if !f.Listen || f.Ephemeral { 696 continue 697 } 698 699 _, ok := topicMap[f.PubsubTopic] 700 if !ok { 701 topicMap[f.PubsubTopic] = make(map[types.TopicType]struct{}) 702 } 703 704 topicMap[f.PubsubTopic][f.ContentTopic] = struct{}{} 705 } 706 707 for pubsubTopic, contentTopics := range topicMap { 708 ctList := maps.Keys(contentTopics) 709 err := t.waku.SetCriteriaForMissingMessageVerification(peerID, pubsubTopic, ctList) 710 if err != nil { 711 t.logger.Error("could not check for missing messages", 712 zap.Error(err), 713 zap.Stringer("peerID", peerID), 714 zap.String("pubsubTopic", pubsubTopic), 715 zap.Stringers("contentTopics", ctList)) 716 return 717 } 718 } 719 }