github.com/onflow/flow-go@v0.33.17/network/p2p/builder/gossipsub/gossipSubBuilder.go (about)

     1  package gossipsubbuilder
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  
     7  	pubsub "github.com/libp2p/go-libp2p-pubsub"
     8  	"github.com/libp2p/go-libp2p/core/host"
     9  	"github.com/libp2p/go-libp2p/core/routing"
    10  	"github.com/rs/zerolog"
    11  
    12  	"github.com/onflow/flow-go/model/flow"
    13  	"github.com/onflow/flow-go/module"
    14  	"github.com/onflow/flow-go/module/irrecoverable"
    15  	"github.com/onflow/flow-go/module/mempool/queue"
    16  	"github.com/onflow/flow-go/module/metrics"
    17  	"github.com/onflow/flow-go/network"
    18  	"github.com/onflow/flow-go/network/p2p"
    19  	p2pbuilderconfig "github.com/onflow/flow-go/network/p2p/builder/config"
    20  	inspectorbuilder "github.com/onflow/flow-go/network/p2p/builder/inspector"
    21  	p2pconfig "github.com/onflow/flow-go/network/p2p/config"
    22  	"github.com/onflow/flow-go/network/p2p/distributor"
    23  	"github.com/onflow/flow-go/network/p2p/inspector/validation"
    24  	p2pnode "github.com/onflow/flow-go/network/p2p/node"
    25  	"github.com/onflow/flow-go/network/p2p/scoring"
    26  	"github.com/onflow/flow-go/network/p2p/tracer"
    27  	"github.com/onflow/flow-go/network/p2p/utils"
    28  	"github.com/onflow/flow-go/utils/logging"
    29  )
    30  
    31  // The Builder struct is used to configure and create a new GossipSub pubsub system.
    32  type Builder struct {
    33  	networkType         network.NetworkingType
    34  	sporkId             flow.Identifier
    35  	logger              zerolog.Logger
    36  	metricsCfg          *p2pbuilderconfig.MetricsConfig
    37  	h                   host.Host
    38  	subscriptionFilter  pubsub.SubscriptionFilter
    39  	gossipSubFactory    p2p.GossipSubFactoryFunc
    40  	gossipSubConfigFunc p2p.GossipSubAdapterConfigFunc
    41  	// gossipSubTracer is a callback interface that is called by the gossipsub implementation upon
    42  	// certain events. Currently, we use it to log and observe the local mesh of the node.
    43  	gossipSubTracer          p2p.PubSubTracer
    44  	scoreOptionConfig        *scoring.ScoreOptionConfig
    45  	idProvider               module.IdentityProvider
    46  	routingSystem            routing.Routing
    47  	rpcInspectorSuiteFactory p2p.GossipSubRpcInspectorSuiteFactoryFunc
    48  	gossipSubCfg             *p2pconfig.GossipSubParameters
    49  }
    50  
    51  var _ p2p.GossipSubBuilder = (*Builder)(nil)
    52  
    53  // SetHost sets the host of the builder.
    54  // If the host has already been set, a fatal error is logged.
    55  func (g *Builder) SetHost(h host.Host) {
    56  	if g.h != nil {
    57  		g.logger.Fatal().Msg("host has already been set")
    58  		return
    59  	}
    60  	g.h = h
    61  }
    62  
    63  // SetSubscriptionFilter sets the subscription filter of the builder.
    64  // If the subscription filter has already been set, a fatal error is logged.
    65  func (g *Builder) SetSubscriptionFilter(subscriptionFilter pubsub.SubscriptionFilter) {
    66  	if g.subscriptionFilter != nil {
    67  		g.logger.Fatal().Msg("subscription filter has already been set")
    68  	}
    69  	g.subscriptionFilter = subscriptionFilter
    70  }
    71  
    72  // SetGossipSubFactory sets the gossipsub factory of the builder.
    73  // We expect the node to initialize with a default gossipsub factory. Hence, this function overrides the default config.
    74  func (g *Builder) SetGossipSubFactory(gossipSubFactory p2p.GossipSubFactoryFunc) {
    75  	if g.gossipSubFactory != nil {
    76  		g.logger.Warn().Msg("gossipsub factory has already been set, overriding the previous factory.")
    77  	}
    78  	g.gossipSubFactory = gossipSubFactory
    79  }
    80  
    81  // SetGossipSubConfigFunc sets the gossipsub config function of the builder.
    82  // We expect the node to initialize with a default gossipsub config. Hence, this function overrides the default config.
    83  func (g *Builder) SetGossipSubConfigFunc(gossipSubConfigFunc p2p.GossipSubAdapterConfigFunc) {
    84  	if g.gossipSubConfigFunc != nil {
    85  		g.logger.Warn().Msg("gossipsub config function has already been set, overriding the previous config function.")
    86  	}
    87  	g.gossipSubConfigFunc = gossipSubConfigFunc
    88  }
    89  
    90  // EnableGossipSubScoringWithOverride enables peer scoring for the GossipSub pubsub system with the given override.
    91  // Any existing peer scoring config attribute that is set in the override will override the default peer scoring config.
    92  // Anything that is left to nil or zero value in the override will be ignored and the default value will be used.
    93  // Note: it is not recommended to override the default peer scoring config in production unless you know what you are doing.
    94  // Production Tip: use PeerScoringConfigNoOverride as the argument to this function to enable peer scoring without any override.
    95  // Args:
    96  // - PeerScoringConfigOverride: override for the peer scoring config- Recommended to use PeerScoringConfigNoOverride for production.
    97  // Returns:
    98  // none
    99  func (g *Builder) EnableGossipSubScoringWithOverride(override *p2p.PeerScoringConfigOverride) {
   100  	g.gossipSubCfg.PeerScoringEnabled = true // TODO: we should enable peer scoring by default.
   101  	if override == nil {
   102  		return
   103  	}
   104  	if override.AppSpecificScoreParams != nil {
   105  		g.logger.Warn().
   106  			Str(logging.KeyNetworkingSecurity, "true").
   107  			Msg("overriding app specific score params for gossipsub")
   108  		g.scoreOptionConfig.OverrideAppSpecificScoreFunction(override.AppSpecificScoreParams)
   109  	}
   110  	if override.TopicScoreParams != nil {
   111  		for topic, params := range override.TopicScoreParams {
   112  			topicLogger := utils.TopicScoreParamsLogger(g.logger, topic.String(), params)
   113  			topicLogger.Warn().
   114  				Str(logging.KeyNetworkingSecurity, "true").
   115  				Msg("overriding topic score params for gossipsub")
   116  			g.scoreOptionConfig.OverrideTopicScoreParams(topic, params)
   117  		}
   118  	}
   119  }
   120  
   121  // SetRoutingSystem sets the routing system of the builder.
   122  // If the routing system has already been set, a fatal error is logged.
   123  func (g *Builder) SetRoutingSystem(routingSystem routing.Routing) {
   124  	if g.routingSystem != nil {
   125  		g.logger.Fatal().Msg("routing system has already been set")
   126  		return
   127  	}
   128  	g.routingSystem = routingSystem
   129  }
   130  
   131  // OverrideDefaultRpcInspectorSuiteFactory overrides the default rpc inspector suite factory.
   132  // Note: this function should only be used for testing purposes. Never override the default rpc inspector suite factory unless you know what you are doing.
   133  func (g *Builder) OverrideDefaultRpcInspectorSuiteFactory(factory p2p.GossipSubRpcInspectorSuiteFactoryFunc) {
   134  	g.logger.Warn().Msg("overriding default rpc inspector suite factory")
   135  	g.rpcInspectorSuiteFactory = factory
   136  }
   137  
   138  // NewGossipSubBuilder returns a new gossipsub builder.
   139  // Args:
   140  // - logger: the logger of the node.
   141  // - metricsCfg: the metrics config of the node.
   142  // - networkType: the network type of the node.
   143  // - sporkId: the spork id of the node.
   144  // - idProvider: the identity provider of the node.
   145  // - rpcInspectorConfig: the rpc inspector config of the node.
   146  // Returns:
   147  // - a new gossipsub builder.
   148  // Note: the builder is not thread-safe. It should only be used in the main thread.
   149  func NewGossipSubBuilder(logger zerolog.Logger,
   150  	metricsCfg *p2pbuilderconfig.MetricsConfig,
   151  	gossipSubCfg *p2pconfig.GossipSubParameters,
   152  	networkType network.NetworkingType,
   153  	sporkId flow.Identifier,
   154  	idProvider module.IdentityProvider) *Builder {
   155  	lg := logger.With().
   156  		Str("component", "gossipsub").
   157  		Str("network-type", networkType.String()).
   158  		Logger()
   159  
   160  	meshTracerCfg := &tracer.GossipSubMeshTracerConfig{
   161  		Logger:                             lg,
   162  		Metrics:                            metricsCfg.Metrics,
   163  		IDProvider:                         idProvider,
   164  		LoggerInterval:                     gossipSubCfg.RpcTracer.LocalMeshLogInterval,
   165  		RpcSentTrackerCacheSize:            gossipSubCfg.RpcTracer.RPCSentTrackerCacheSize,
   166  		RpcSentTrackerWorkerQueueCacheSize: gossipSubCfg.RpcTracer.RPCSentTrackerQueueCacheSize,
   167  		RpcSentTrackerNumOfWorkers:         gossipSubCfg.RpcTracer.RpcSentTrackerNumOfWorkers,
   168  		HeroCacheMetricsFactory:            metricsCfg.HeroCacheFactory,
   169  		NetworkingType:                     networkType,
   170  	}
   171  	meshTracer := tracer.NewGossipSubMeshTracer(meshTracerCfg)
   172  
   173  	b := &Builder{
   174  		logger:                   lg,
   175  		metricsCfg:               metricsCfg,
   176  		sporkId:                  sporkId,
   177  		networkType:              networkType,
   178  		idProvider:               idProvider,
   179  		gossipSubFactory:         defaultGossipSubFactory(),
   180  		gossipSubConfigFunc:      defaultGossipSubAdapterConfig(),
   181  		scoreOptionConfig:        scoring.NewScoreOptionConfig(lg, gossipSubCfg.ScoringParameters, metricsCfg.HeroCacheFactory, idProvider, networkType),
   182  		rpcInspectorSuiteFactory: defaultInspectorSuite(meshTracer),
   183  		gossipSubTracer:          meshTracer,
   184  		gossipSubCfg:             gossipSubCfg,
   185  	}
   186  
   187  	return b
   188  }
   189  
   190  // defaultGossipSubFactory returns the default gossipsub factory function. It is used to create the default gossipsub factory.
   191  // Note: always use the default gossipsub factory function to create the gossipsub factory (unless you know what you are doing).
   192  func defaultGossipSubFactory() p2p.GossipSubFactoryFunc {
   193  	return func(ctx context.Context, logger zerolog.Logger, h host.Host, cfg p2p.PubSubAdapterConfig, clusterChangeConsumer p2p.CollectionClusterChangesConsumer) (p2p.PubSubAdapter, error) {
   194  		return p2pnode.NewGossipSubAdapter(ctx, logger, h, cfg, clusterChangeConsumer)
   195  	}
   196  }
   197  
   198  // defaultGossipSubAdapterConfig returns the default gossipsub config function. It is used to create the default gossipsub config.
   199  // Note: always use the default gossipsub config function to create the gossipsub config (unless you know what you are doing).
   200  func defaultGossipSubAdapterConfig() p2p.GossipSubAdapterConfigFunc {
   201  	return func(cfg *p2p.BasePubSubAdapterConfig) p2p.PubSubAdapterConfig {
   202  		return p2pnode.NewGossipSubAdapterConfig(cfg)
   203  	}
   204  }
   205  
   206  // defaultInspectorSuite returns the default inspector suite factory function. It is used to create the default inspector suite.
   207  // Inspector suite is utilized to inspect the incoming gossipsub rpc messages from different perspectives.
   208  // Note: always use the default inspector suite factory function to create the inspector suite (unless you know what you are doing).
   209  // todo: this function can be simplified.
   210  func defaultInspectorSuite(rpcTracker p2p.RpcControlTracking) p2p.GossipSubRpcInspectorSuiteFactoryFunc {
   211  	return func(ctx irrecoverable.SignalerContext,
   212  		logger zerolog.Logger,
   213  		sporkId flow.Identifier,
   214  		inspectorCfg *p2pconfig.RpcInspectorParameters,
   215  		gossipSubMetrics module.GossipSubMetrics,
   216  		heroCacheMetricsFactory metrics.HeroCacheMetricsFactory,
   217  		networkType network.NetworkingType,
   218  		idProvider module.IdentityProvider,
   219  		topicProvider func() p2p.TopicProvider) (p2p.GossipSubInspectorSuite, error) {
   220  
   221  		notificationDistributor := distributor.DefaultGossipSubInspectorNotificationDistributor(logger, []queue.HeroStoreConfigOption{
   222  			queue.WithHeroStoreSizeLimit(inspectorCfg.NotificationCacheSize),
   223  			queue.WithHeroStoreCollector(metrics.RpcInspectorNotificationQueueMetricFactory(heroCacheMetricsFactory, networkType))}...)
   224  
   225  		params := &validation.InspectorParams{
   226  			Logger:                  logger,
   227  			SporkID:                 sporkId,
   228  			Config:                  &inspectorCfg.Validation,
   229  			Distributor:             notificationDistributor,
   230  			HeroCacheMetricsFactory: heroCacheMetricsFactory,
   231  			IdProvider:              idProvider,
   232  			InspectorMetrics:        gossipSubMetrics,
   233  			RpcTracker:              rpcTracker,
   234  			NetworkingType:          networkType,
   235  			TopicOracle:             topicProvider,
   236  		}
   237  		rpcValidationInspector, err := validation.NewControlMsgValidationInspector(params)
   238  		if err != nil {
   239  			return nil, fmt.Errorf("failed to create new control message valiadation inspector: %w", err)
   240  		}
   241  		return inspectorbuilder.NewGossipSubInspectorSuite(rpcValidationInspector, notificationDistributor), nil
   242  	}
   243  }
   244  
   245  // Build creates a new GossipSub pubsub system.
   246  // It returns the newly created GossipSub pubsub system and any errors encountered during its creation.
   247  // Arguments:
   248  // - ctx: the irrecoverable context of the node.
   249  //
   250  // Returns:
   251  // - p2p.PubSubAdapter: a GossipSub pubsub system for the libp2p node.
   252  // - p2p.PeerScoreTracer: a peer score tracer for the GossipSub pubsub system (if enabled, otherwise nil).
   253  // - error: if an error occurs during the creation of the GossipSub pubsub system, it is returned. Otherwise, nil is returned.
   254  // Note that on happy path, the returned error is nil. Any error returned is unexpected and should be handled as irrecoverable.
   255  func (g *Builder) Build(ctx irrecoverable.SignalerContext) (p2p.PubSubAdapter, error) {
   256  	// placeholder for the gossipsub pubsub system that will be created (so that it can be passed around even
   257  	// before it is created).
   258  	var gossipSub p2p.PubSubAdapter
   259  
   260  	gossipSubConfigs := g.gossipSubConfigFunc(&p2p.BasePubSubAdapterConfig{
   261  		MaxMessageSize: p2pnode.DefaultMaxPubSubMsgSize,
   262  	})
   263  	gossipSubConfigs.WithMessageIdFunction(utils.MessageID)
   264  
   265  	if g.routingSystem != nil {
   266  		gossipSubConfigs.WithRoutingDiscovery(g.routingSystem)
   267  	}
   268  
   269  	if g.subscriptionFilter != nil {
   270  		gossipSubConfigs.WithSubscriptionFilter(g.subscriptionFilter)
   271  	}
   272  
   273  	inspectorSuite, err := g.rpcInspectorSuiteFactory(ctx,
   274  		g.logger,
   275  		g.sporkId,
   276  		&g.gossipSubCfg.RpcInspector,
   277  		g.metricsCfg.Metrics,
   278  		g.metricsCfg.HeroCacheFactory,
   279  		g.networkType,
   280  		g.idProvider,
   281  		func() p2p.TopicProvider {
   282  			return gossipSub
   283  		})
   284  	if err != nil {
   285  		return nil, fmt.Errorf("could not create gossipsub inspector suite: %w", err)
   286  	}
   287  	gossipSubConfigs.WithInspectorSuite(inspectorSuite)
   288  
   289  	var scoreOpt *scoring.ScoreOption
   290  	var scoreTracer p2p.PeerScoreTracer
   291  	// currently, peer scoring is not supported for public networks.
   292  	if g.gossipSubCfg.PeerScoringEnabled && g.networkType != network.PublicNetwork {
   293  		// wires the gossipsub score option to the subscription provider.
   294  		subscriptionProvider, err := scoring.NewSubscriptionProvider(&scoring.SubscriptionProviderConfig{
   295  			Logger: g.logger,
   296  			TopicProviderOracle: func() p2p.TopicProvider {
   297  				// gossipSub has not been created yet, hence instead of passing it directly, we pass a function that returns it.
   298  				// the cardinal assumption is this function is only invoked when the subscription provider is started, which is
   299  				// after the gossipsub is created.
   300  				return gossipSub
   301  			},
   302  			IdProvider:              g.idProvider,
   303  			Params:                  &g.gossipSubCfg.SubscriptionProvider,
   304  			HeroCacheMetricsFactory: g.metricsCfg.HeroCacheFactory,
   305  			NetworkingType:          g.networkType,
   306  		})
   307  		if err != nil {
   308  			return nil, fmt.Errorf("could not create subscription provider: %w", err)
   309  		}
   310  
   311  		g.scoreOptionConfig.SetRegisterNotificationConsumerFunc(inspectorSuite.AddInvalidControlMessageConsumer)
   312  		scoreOpt, err = scoring.NewScoreOption(g.scoreOptionConfig, subscriptionProvider)
   313  		if err != nil {
   314  			return nil, fmt.Errorf("could not create gossipsub score option: %w", err)
   315  		}
   316  		gossipSubConfigs.WithScoreOption(scoreOpt)
   317  
   318  		if g.gossipSubCfg.RpcTracer.ScoreTracerInterval > 0 {
   319  			scoreTracer = tracer.NewGossipSubScoreTracer(g.logger, g.idProvider, g.metricsCfg.Metrics, g.gossipSubCfg.RpcTracer.ScoreTracerInterval)
   320  			gossipSubConfigs.WithScoreTracer(scoreTracer)
   321  		}
   322  	} else {
   323  		g.logger.Warn().
   324  			Str(logging.KeyNetworkingSecurity, "true").
   325  			Msg("gossipsub peer scoring is disabled")
   326  	}
   327  
   328  	if g.gossipSubTracer != nil {
   329  		gossipSubConfigs.WithTracer(g.gossipSubTracer)
   330  	}
   331  
   332  	if g.h == nil {
   333  		return nil, fmt.Errorf("could not create gossipsub: host is nil")
   334  	}
   335  
   336  	gossipSub, err = g.gossipSubFactory(ctx, g.logger, g.h, gossipSubConfigs, inspectorSuite)
   337  	if err != nil {
   338  		return nil, fmt.Errorf("could not create gossipsub: %w", err)
   339  	}
   340  
   341  	return gossipSub, nil
   342  }