github.com/koko1123/flow-go-1@v0.29.6/network/p2p/scoring/subscription_provider.go (about) 1 package scoring 2 3 import ( 4 "sync" 5 6 "github.com/libp2p/go-libp2p/core/peer" 7 "github.com/rs/zerolog" 8 "go.uber.org/atomic" 9 10 "github.com/koko1123/flow-go-1/network/p2p" 11 ) 12 13 // SubscriptionProvider provides a list of topics a peer is subscribed to. 14 type SubscriptionProvider struct { 15 logger zerolog.Logger 16 tp p2p.TopicProvider 17 18 // allTopics is a list of all topics in the pubsub network 19 // TODO: we should add an expiry time to this cache and clean up the cache periodically 20 // to avoid leakage of stale topics. 21 peersByTopic sync.Map // map[topic]peers 22 peersByTopicUpdating sync.Map // whether a goroutine is already updating the list of peers for a topic 23 24 // allTopics is a list of all topics in the pubsub network that this node is subscribed to. 25 allTopicsLock sync.RWMutex // protects allTopics 26 allTopics []string // list of all topics in the pubsub network that this node has subscribed to. 27 allTopicsUpdate atomic.Bool // whether a goroutine is already updating the list of topics. 28 } 29 30 func NewSubscriptionProvider(logger zerolog.Logger, tp p2p.TopicProvider) *SubscriptionProvider { 31 return &SubscriptionProvider{ 32 logger: logger.With().Str("module", "subscription_provider").Logger(), 33 tp: tp, 34 allTopics: make([]string, 0), 35 } 36 } 37 38 // GetSubscribedTopics returns all the subscriptions of a peer within the pubsub network. 39 // Note that the current node can only see peer subscriptions to topics that it has also subscribed to 40 // e.g., if current node has subscribed to topics A and B, and peer1 has subscribed to topics A, B, and C, 41 // then GetSubscribedTopics(peer1) will return A and B. Since this node has not subscribed to topic C, 42 // it will not be able to query for other peers subscribed to topic C. 43 func (s *SubscriptionProvider) GetSubscribedTopics(pid peer.ID) []string { 44 topics := s.getAllTopics() 45 46 // finds the topics that this peer is subscribed to. 47 subscriptions := make([]string, 0) 48 for _, topic := range topics { 49 peers := s.getPeersByTopic(topic) 50 for _, p := range peers { 51 if p == pid { 52 subscriptions = append(subscriptions, topic) 53 } 54 } 55 } 56 57 return subscriptions 58 } 59 60 // getAllTopics returns all the topics in the pubsub network that this node (peer) has subscribed to. 61 // Note that this method always returns the cached version of the subscribed topics while querying the 62 // pubsub network for the list of topics in a goroutine. Hence, the first call to this method always returns an empty 63 // list. 64 func (s *SubscriptionProvider) getAllTopics() []string { 65 go func() { 66 // TODO: refactor this to a component manager worker once we have a startable libp2p node. 67 if updateInProgress := s.allTopicsUpdate.CompareAndSwap(false, true); updateInProgress { 68 // another goroutine is already updating the list of topics 69 return 70 } 71 72 allTopics := s.tp.GetTopics() 73 s.atomicUpdateAllTopics(allTopics) 74 75 // remove the update flag 76 s.allTopicsUpdate.Store(false) 77 78 s.logger.Trace().Msgf("all topics updated: %v", allTopics) 79 }() 80 81 s.allTopicsLock.RLock() 82 defer s.allTopicsLock.RUnlock() 83 return s.allTopics 84 } 85 86 // getPeersByTopic returns all the peers subscribed to a topic. 87 // Note that this method always returns the cached version of the subscribed peers while querying the 88 // pubsub network for the list of topics in a goroutine. Hence, the first call to this method always returns an empty 89 // list. 90 // As this method is injected into GossipSub, it is vital that it never block the caller, otherwise it causes a 91 // deadlock on the GossipSub. 92 // Also note that, this peer itself should be subscribed to the topic, otherwise, it cannot find the list of peers 93 // subscribed to the topic in the pubsub network due to an inherent limitation of GossipSub. 94 func (s *SubscriptionProvider) getPeersByTopic(topic string) []peer.ID { 95 go func() { 96 // TODO: refactor this to a component manager worker once we have a startable libp2p node. 97 if _, updateInProgress := s.peersByTopicUpdating.LoadOrStore(topic, true); updateInProgress { 98 // another goroutine is already updating the list of peers for this topic 99 return 100 } 101 102 subscribedPeers := s.tp.ListPeers(topic) 103 s.peersByTopic.Store(topic, subscribedPeers) 104 105 // remove the update flag 106 s.peersByTopicUpdating.Delete(topic) 107 108 s.logger.Trace().Str("topic", topic).Msgf("peers by topic updated: %v", subscribedPeers) 109 }() 110 111 peerId, ok := s.peersByTopic.Load(topic) 112 if !ok { 113 return make([]peer.ID, 0) 114 } 115 return peerId.([]peer.ID) 116 } 117 118 // atomicUpdateAllTopics updates the list of all topics in the pubsub network that this node has subscribed to. 119 func (s *SubscriptionProvider) atomicUpdateAllTopics(allTopics []string) { 120 s.allTopicsLock.Lock() 121 s.allTopics = allTopics 122 s.allTopicsLock.Unlock() 123 }