github.com/onflow/flow-go@v0.33.17/network/p2p/distributor/gossipsub_inspector.go (about) 1 package distributor 2 3 import ( 4 "sync" 5 6 "github.com/rs/zerolog" 7 8 "github.com/onflow/flow-go/engine" 9 "github.com/onflow/flow-go/engine/common/worker" 10 "github.com/onflow/flow-go/module/component" 11 "github.com/onflow/flow-go/module/mempool/queue" 12 "github.com/onflow/flow-go/module/metrics" 13 "github.com/onflow/flow-go/network/p2p" 14 p2plogging "github.com/onflow/flow-go/network/p2p/logging" 15 ) 16 17 const ( 18 // DefaultGossipSubInspectorNotificationQueueCacheSize is the default cache size for the gossipsub rpc inspector notification queue. 19 DefaultGossipSubInspectorNotificationQueueCacheSize = 10_000 20 // defaultGossipSubInspectorNotificationQueueWorkerCount is the default number of workers that will process the gossipsub rpc inspector notifications. 21 defaultGossipSubInspectorNotificationQueueWorkerCount = 1 22 ) 23 24 var _ p2p.GossipSubInspectorNotifDistributor = (*GossipSubInspectorNotifDistributor)(nil) 25 26 // GossipSubInspectorNotifDistributor is a component that distributes gossipsub rpc inspector notifications to 27 // registered consumers in a non-blocking manner and asynchronously. It is thread-safe and can be used concurrently from 28 // multiple goroutines. The distribution is done by a worker pool. The worker pool is configured with a queue that has a 29 // fixed size. If the queue is full, the notification is discarded. The queue size and the number of workers can be 30 // configured. 31 type GossipSubInspectorNotifDistributor struct { 32 component.Component 33 cm *component.ComponentManager 34 logger zerolog.Logger 35 36 workerPool *worker.Pool[*p2p.InvCtrlMsgNotif] 37 consumerLock sync.RWMutex // protects the consumer field from concurrent updates 38 consumers []p2p.GossipSubInvCtrlMsgNotifConsumer 39 } 40 41 // DefaultGossipSubInspectorNotificationDistributor returns a new GossipSubInspectorNotifDistributor component with the default configuration. 42 func DefaultGossipSubInspectorNotificationDistributor(logger zerolog.Logger, opts ...queue.HeroStoreConfigOption) *GossipSubInspectorNotifDistributor { 43 cfg := &queue.HeroStoreConfig{ 44 SizeLimit: DefaultGossipSubInspectorNotificationQueueCacheSize, 45 Collector: metrics.NewNoopCollector(), 46 } 47 48 for _, opt := range opts { 49 opt(cfg) 50 } 51 52 store := queue.NewHeroStore(cfg.SizeLimit, logger, cfg.Collector) 53 return NewGossipSubInspectorNotificationDistributor(logger, store) 54 } 55 56 // NewGossipSubInspectorNotificationDistributor returns a new GossipSubInspectorNotifDistributor component. 57 // It takes a message store to store the notifications in memory and process them asynchronously. 58 func NewGossipSubInspectorNotificationDistributor(log zerolog.Logger, store engine.MessageStore) *GossipSubInspectorNotifDistributor { 59 lg := log.With().Str("component", "gossipsub_rpc_inspector_distributor").Logger() 60 61 d := &GossipSubInspectorNotifDistributor{ 62 logger: lg, 63 } 64 65 pool := worker.NewWorkerPoolBuilder[*p2p.InvCtrlMsgNotif](lg, store, d.distribute).Build() 66 d.workerPool = pool 67 68 cm := component.NewComponentManagerBuilder() 69 70 for i := 0; i < defaultGossipSubInspectorNotificationQueueWorkerCount; i++ { 71 cm.AddWorker(pool.WorkerLogic()) 72 } 73 74 d.cm = cm.Build() 75 d.Component = d.cm 76 77 return d 78 } 79 80 // Distribute distributes the gossipsub rpc inspector notification to all registered consumers. 81 // The distribution is done asynchronously and non-blocking. The notification is added to a queue and processed by a worker pool. 82 // DistributeEvent in this implementation does not return an error, but it logs a warning if the queue is full. 83 func (g *GossipSubInspectorNotifDistributor) Distribute(notification *p2p.InvCtrlMsgNotif) error { 84 lg := g.logger.With().Str("peer_id", p2plogging.PeerId(notification.PeerID)).Logger() 85 if ok := g.workerPool.Submit(notification); !ok { 86 // we use a queue with a fixed size, so this can happen when queue is full or when the notification is duplicate. 87 lg.Warn().Msg("gossipsub rpc inspector notification queue is full or notification is duplicate, discarding notification") 88 } 89 lg.Trace().Msg("gossipsub rpc inspector notification submitted to the queue") 90 return nil 91 } 92 93 // AddConsumer adds a consumer to the distributor. The consumer will be called when distributor distributes a new event. 94 // AddConsumer must be concurrency safe. Once a consumer is added, it must be called for all future events. 95 // There is no guarantee that the consumer will be called for events that were already received by the distributor. 96 func (g *GossipSubInspectorNotifDistributor) AddConsumer(consumer p2p.GossipSubInvCtrlMsgNotifConsumer) { 97 g.consumerLock.Lock() 98 defer g.consumerLock.Unlock() 99 100 g.consumers = append(g.consumers, consumer) 101 } 102 103 // distribute calls the ConsumeEvent method of all registered consumers. It is called by the workers of the worker pool. 104 // It is concurrency safe and can be called concurrently by multiple workers. However, the consumers may be blocking 105 // on the ConsumeEvent method. 106 func (g *GossipSubInspectorNotifDistributor) distribute(notification *p2p.InvCtrlMsgNotif) error { 107 g.consumerLock.RLock() 108 defer g.consumerLock.RUnlock() 109 110 g.logger.Trace().Msg("distributing gossipsub rpc inspector notification") 111 for _, consumer := range g.consumers { 112 consumer.OnInvalidControlMessageNotification(notification) 113 } 114 g.logger.Trace().Msg("gossipsub rpc inspector notification distributed") 115 116 return nil 117 }