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  }