github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/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/metrics"
    16  	"github.com/onflow/flow-go/network"
    17  	"github.com/onflow/flow-go/network/p2p"
    18  	p2pbuilderconfig "github.com/onflow/flow-go/network/p2p/builder/config"
    19  	p2pconfig "github.com/onflow/flow-go/network/p2p/config"
    20  	"github.com/onflow/flow-go/network/p2p/inspector/validation"
    21  	p2pnode "github.com/onflow/flow-go/network/p2p/node"
    22  	"github.com/onflow/flow-go/network/p2p/scoring"
    23  	"github.com/onflow/flow-go/network/p2p/tracer"
    24  	"github.com/onflow/flow-go/network/p2p/utils"
    25  	"github.com/onflow/flow-go/utils/logging"
    26  )
    27  
    28  // The Builder struct is used to configure and create a new GossipSub pubsub system.
    29  type Builder struct {
    30  	networkType         network.NetworkingType
    31  	sporkId             flow.Identifier
    32  	logger              zerolog.Logger
    33  	metricsCfg          *p2pbuilderconfig.MetricsConfig
    34  	h                   host.Host
    35  	subscriptionFilter  pubsub.SubscriptionFilter
    36  	gossipSubFactory    p2p.GossipSubFactoryFunc
    37  	gossipSubConfigFunc p2p.GossipSubAdapterConfigFunc
    38  	rpcInspectorFactory p2p.GossipSubRpcInspectorFactoryFunc
    39  	// gossipSubTracer is a callback interface that is called by the gossipsub implementation upon
    40  	// certain events. Currently, we use it to log and observe the local mesh of the node.
    41  	gossipSubTracer   p2p.PubSubTracer
    42  	scoreOptionConfig *scoring.ScoreOptionConfig
    43  	idProvider        module.IdentityProvider
    44  	routingSystem     routing.Routing
    45  	gossipSubCfg      *p2pconfig.GossipSubParameters
    46  }
    47  
    48  var _ p2p.GossipSubBuilder = (*Builder)(nil)
    49  
    50  // SetHost sets the host of the builder.
    51  // If the host has already been set, a fatal error is logged.
    52  func (g *Builder) SetHost(h host.Host) {
    53  	if g.h != nil {
    54  		g.logger.Fatal().Msg("host has already been set")
    55  		return
    56  	}
    57  	g.h = h
    58  }
    59  
    60  // OverrideDefaultRpcInspectorFactory overrides the default rpc inspector factory of the builder.
    61  // If the rpc inspector factory has already been set, a warning is logged.
    62  // Note: it is not recommended to override the default rpc inspector factory in production unless you know what you are doing.
    63  // The purpose of this function is to allow for testing and development.
    64  // Args:
    65  // - factoryFunc: the factory function to override the default rpc inspector factory.
    66  // Returns:
    67  // none
    68  func (g *Builder) OverrideDefaultRpcInspectorFactory(factoryFunc p2p.GossipSubRpcInspectorFactoryFunc) {
    69  	g.logger.Warn().Bool(logging.KeySuspicious, true).Msg("overriding default rpc inspector factory, not recommended for production")
    70  	g.rpcInspectorFactory = factoryFunc
    71  }
    72  
    73  // SetSubscriptionFilter sets the subscription filter of the builder.
    74  // If the subscription filter has already been set, a fatal error is logged.
    75  func (g *Builder) SetSubscriptionFilter(subscriptionFilter pubsub.SubscriptionFilter) {
    76  	if g.subscriptionFilter != nil {
    77  		g.logger.Fatal().Msg("subscription filter has already been set")
    78  	}
    79  	g.subscriptionFilter = subscriptionFilter
    80  }
    81  
    82  // SetGossipSubFactory sets the gossipsub factory of the builder.
    83  // We expect the node to initialize with a default gossipsub factory. Hence, this function overrides the default config.
    84  func (g *Builder) SetGossipSubFactory(gossipSubFactory p2p.GossipSubFactoryFunc) {
    85  	if g.gossipSubFactory != nil {
    86  		g.logger.Warn().Msg("gossipsub factory has already been set, overriding the previous factory.")
    87  	}
    88  	g.gossipSubFactory = gossipSubFactory
    89  }
    90  
    91  // SetGossipSubConfigFunc sets the gossipsub config function of the builder.
    92  // We expect the node to initialize with a default gossipsub config. Hence, this function overrides the default config.
    93  func (g *Builder) SetGossipSubConfigFunc(gossipSubConfigFunc p2p.GossipSubAdapterConfigFunc) {
    94  	if g.gossipSubConfigFunc != nil {
    95  		g.logger.Warn().Msg("gossipsub config function has already been set, overriding the previous config function.")
    96  	}
    97  	g.gossipSubConfigFunc = gossipSubConfigFunc
    98  }
    99  
   100  // EnableGossipSubScoringWithOverride enables peer scoring for the GossipSub pubsub system with the given override.
   101  // Any existing peer scoring config attribute that is set in the override will override the default peer scoring config.
   102  // Anything that is left to nil or zero value in the override will be ignored and the default value will be used.
   103  // Note: it is not recommended to override the default peer scoring config in production unless you know what you are doing.
   104  // Production Tip: use PeerScoringConfigNoOverride as the argument to this function to enable peer scoring without any override.
   105  // Args:
   106  // - PeerScoringConfigOverride: override for the peer scoring config- Recommended to use PeerScoringConfigNoOverride for production.
   107  // Returns:
   108  // none
   109  func (g *Builder) EnableGossipSubScoringWithOverride(override *p2p.PeerScoringConfigOverride) {
   110  	g.gossipSubCfg.PeerScoringEnabled = true // TODO: we should enable peer scoring by default.
   111  	if override == nil {
   112  		return
   113  	}
   114  	if override.AppSpecificScoreParams != nil {
   115  		g.logger.Warn().
   116  			Str(logging.KeyNetworkingSecurity, "true").
   117  			Msg("overriding app specific score params for gossipsub")
   118  		g.scoreOptionConfig.OverrideAppSpecificScoreFunction(override.AppSpecificScoreParams)
   119  	}
   120  	if override.TopicScoreParams != nil {
   121  		for topic, params := range override.TopicScoreParams {
   122  			topicLogger := utils.TopicScoreParamsLogger(g.logger, topic.String(), params)
   123  			topicLogger.Warn().
   124  				Str(logging.KeyNetworkingSecurity, "true").
   125  				Msg("overriding topic score params for gossipsub")
   126  			g.scoreOptionConfig.OverrideTopicScoreParams(topic, params)
   127  		}
   128  	}
   129  }
   130  
   131  // SetRoutingSystem sets the routing system of the builder.
   132  // If the routing system has already been set, a fatal error is logged.
   133  func (g *Builder) SetRoutingSystem(routingSystem routing.Routing) {
   134  	if g.routingSystem != nil {
   135  		g.logger.Fatal().Msg("routing system has already been set")
   136  		return
   137  	}
   138  	g.routingSystem = routingSystem
   139  }
   140  
   141  // NewGossipSubBuilder returns a new gossipsub builder.
   142  // Args:
   143  // - logger: the logger of the node.
   144  // - metricsCfg: the metrics config of the node.
   145  // - networkType: the network type of the node.
   146  // - sporkId: the spork id of the node.
   147  // - idProvider: the identity provider of the node.
   148  // - rpcInspectorConfig: the rpc inspector config of the node.
   149  // - subscriptionProviderPrams: the subscription provider params of the node.
   150  // - meshTracer: gossipsub mesh tracer.
   151  // Returns:
   152  // - a new gossipsub builder.
   153  // Note: the builder is not thread-safe. It should only be used in the main thread.
   154  func NewGossipSubBuilder(logger zerolog.Logger,
   155  	metricsCfg *p2pbuilderconfig.MetricsConfig,
   156  	gossipSubCfg *p2pconfig.GossipSubParameters,
   157  	networkType network.NetworkingType,
   158  	sporkId flow.Identifier,
   159  	idProvider module.IdentityProvider) *Builder {
   160  	lg := logger.With().
   161  		Str("component", "gossipsub").
   162  		Str("network-type", networkType.String()).
   163  		Logger()
   164  
   165  	meshTracerCfg := &tracer.GossipSubMeshTracerConfig{
   166  		Logger:         lg,
   167  		Metrics:        metricsCfg.Metrics,
   168  		IDProvider:     idProvider,
   169  		LoggerInterval: gossipSubCfg.RpcTracer.LocalMeshLogInterval,
   170  		RpcSentTracker: tracer.RpcSentTrackerConfig{
   171  			CacheSize:            gossipSubCfg.RpcTracer.RPCSentTrackerCacheSize,
   172  			WorkerQueueCacheSize: gossipSubCfg.RpcTracer.RPCSentTrackerQueueCacheSize,
   173  			WorkerQueueNumber:    gossipSubCfg.RpcTracer.RpcSentTrackerNumOfWorkers,
   174  		},
   175  		DuplicateMessageTrackerCacheConfig: gossipSubCfg.RpcTracer.DuplicateMessageTrackerConfig,
   176  		HeroCacheMetricsFactory:            metricsCfg.HeroCacheFactory,
   177  		NetworkingType:                     networkType,
   178  	}
   179  	meshTracer := tracer.NewGossipSubMeshTracer(meshTracerCfg)
   180  
   181  	b := &Builder{
   182  		logger:              lg,
   183  		metricsCfg:          metricsCfg,
   184  		sporkId:             sporkId,
   185  		networkType:         networkType,
   186  		idProvider:          idProvider,
   187  		gossipSubFactory:    defaultGossipSubFactory(),
   188  		gossipSubConfigFunc: defaultGossipSubAdapterConfig(),
   189  		scoreOptionConfig: scoring.NewScoreOptionConfig(lg,
   190  			gossipSubCfg.ScoringParameters,
   191  			metricsCfg.HeroCacheFactory,
   192  			metricsCfg.Metrics,
   193  			idProvider,
   194  			meshTracer.DuplicateMessageCount,
   195  			networkType,
   196  		),
   197  		gossipSubTracer:     meshTracer,
   198  		gossipSubCfg:        gossipSubCfg,
   199  		rpcInspectorFactory: defaultRpcInspectorFactory(meshTracer),
   200  	}
   201  
   202  	return b
   203  }
   204  
   205  // defaultRpcInspectorFactory returns the default rpc inspector factory function. It is used to create the default rpc inspector factory.
   206  // Note: always use the default rpc inspector factory function to create the rpc inspector factory (unless you know what you are doing).
   207  // Args:
   208  // - tracer: the tracer of the node.
   209  // Returns:
   210  // - a new rpc inspector factory function.
   211  func defaultRpcInspectorFactory(tracer p2p.PubSubTracer) p2p.GossipSubRpcInspectorFactoryFunc {
   212  	return func(logger zerolog.Logger,
   213  		sporkId flow.Identifier,
   214  		rpcInspectorConfig *p2pconfig.RpcInspectorParameters,
   215  		inspectorMetrics module.GossipSubMetrics,
   216  		heroCacheMetrics metrics.HeroCacheMetricsFactory,
   217  		networkingType network.NetworkingType,
   218  		idProvider module.IdentityProvider,
   219  		topicProvider func() p2p.TopicProvider,
   220  		notificationConsumer p2p.GossipSubInvCtrlMsgNotifConsumer) (p2p.GossipSubRPCInspector, error) {
   221  		return validation.NewControlMsgValidationInspector(&validation.InspectorParams{
   222  			Logger:                  logger.With().Str("component", "rpc-inspector").Logger(),
   223  			SporkID:                 sporkId,
   224  			Config:                  &rpcInspectorConfig.Validation,
   225  			HeroCacheMetricsFactory: heroCacheMetrics,
   226  			IdProvider:              idProvider,
   227  			InspectorMetrics:        inspectorMetrics,
   228  			RpcTracker:              tracer,
   229  			NetworkingType:          networkingType,
   230  			InvalidControlMessageNotificationConsumer: notificationConsumer,
   231  			TopicOracle: topicProvider,
   232  		})
   233  	}
   234  }
   235  
   236  // defaultGossipSubFactory returns the default gossipsub factory function. It is used to create the default gossipsub factory.
   237  // Note: always use the default gossipsub factory function to create the gossipsub factory (unless you know what you are doing).
   238  func defaultGossipSubFactory() p2p.GossipSubFactoryFunc {
   239  	return func(ctx context.Context, logger zerolog.Logger, h host.Host, cfg p2p.PubSubAdapterConfig, clusterChangeConsumer p2p.CollectionClusterChangesConsumer) (p2p.PubSubAdapter, error) {
   240  		return p2pnode.NewGossipSubAdapter(ctx, logger, h, cfg, clusterChangeConsumer)
   241  	}
   242  }
   243  
   244  // defaultGossipSubAdapterConfig returns the default gossipsub config function. It is used to create the default gossipsub config.
   245  // Note: always use the default gossipsub config function to create the gossipsub config (unless you know what you are doing).
   246  func defaultGossipSubAdapterConfig() p2p.GossipSubAdapterConfigFunc {
   247  	return func(cfg *p2p.BasePubSubAdapterConfig) p2p.PubSubAdapterConfig {
   248  		return p2pnode.NewGossipSubAdapterConfig(cfg)
   249  	}
   250  }
   251  
   252  // Build creates a new GossipSub pubsub system.
   253  // It returns the newly created GossipSub pubsub system and any errors encountered during its creation.
   254  // Arguments:
   255  // - ctx: the irrecoverable context of the node.
   256  //
   257  // Returns:
   258  // - p2p.PubSubAdapter: a GossipSub pubsub system for the libp2p node.
   259  // - p2p.PeerScoreTracer: a peer score tracer for the GossipSub pubsub system (if enabled, otherwise nil).
   260  // - error: if an error occurs during the creation of the GossipSub pubsub system, it is returned. Otherwise, nil is returned.
   261  // Note that on happy path, the returned error is nil. Any error returned is unexpected and should be handled as irrecoverable.
   262  func (g *Builder) Build(ctx irrecoverable.SignalerContext) (p2p.PubSubAdapter, error) {
   263  	// placeholder for the gossipsub pubsub system that will be created (so that it can be passed around even
   264  	// before it is created).
   265  	var gossipSub p2p.PubSubAdapter
   266  
   267  	gossipSubConfigs := g.gossipSubConfigFunc(&p2p.BasePubSubAdapterConfig{
   268  		MaxMessageSize: p2pnode.DefaultMaxPubSubMsgSize,
   269  	})
   270  	gossipSubConfigs.WithMessageIdFunction(utils.MessageID)
   271  
   272  	if g.routingSystem != nil {
   273  		gossipSubConfigs.WithRoutingDiscovery(g.routingSystem)
   274  	}
   275  
   276  	if g.subscriptionFilter != nil {
   277  		gossipSubConfigs.WithSubscriptionFilter(g.subscriptionFilter)
   278  	}
   279  
   280  	// scoreOpt is the score option for the GossipSub pubsub system. It is a self-contained component that is used carry over the
   281  	// peer scoring parameters (including the entire app-specific score function) and inject it into the GossipSub pubsub system at creation time.
   282  	var scoreOpt *scoring.ScoreOption
   283  	// scoreTracer is the peer score tracer for the GossipSub pubsub system. It is used to trace the peer scores.
   284  	// It is only created if peer scoring is enabled. Otherwise, it is nil.
   285  	var scoreTracer p2p.PeerScoreTracer
   286  	// consumer is the consumer of the invalid control message notifications; i.e., the component that should be nlotified when
   287  	// an RPC validation fails. This component is responsible for taking action on the notification. Currently, the score option
   288  	// is the consumer of the invalid control message notifications.
   289  	// When the peer scoring is disabled, the consumer is a no-op consumer.
   290  	var consumer p2p.GossipSubInvCtrlMsgNotifConsumer
   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  		scoreOpt, err = scoring.NewScoreOption(g.scoreOptionConfig, subscriptionProvider)
   311  		if err != nil {
   312  			return nil, fmt.Errorf("could not create gossipsub score option: %w", err)
   313  		}
   314  		gossipSubConfigs.WithScoreOption(scoreOpt)
   315  		consumer = scoreOpt // the score option is the consumer of the invalid control message notifications.
   316  
   317  		if g.gossipSubCfg.RpcTracer.ScoreTracerInterval > 0 {
   318  			scoreTracer = tracer.NewGossipSubScoreTracer(g.logger, g.idProvider, g.metricsCfg.Metrics, g.gossipSubCfg.RpcTracer.ScoreTracerInterval)
   319  			gossipSubConfigs.WithScoreTracer(scoreTracer)
   320  		}
   321  	} else {
   322  		g.logger.Warn().
   323  			Str(logging.KeyNetworkingSecurity, "true").
   324  			Msg("gossipsub peer scoring is disabled, no-op consumer will be used for invalid control message notifications.")
   325  		consumer = scoring.NewNoopInvCtrlMsgNotifConsumer() // no-op consumer as peer scoring is disabled.
   326  	}
   327  
   328  	rpcValidationInspector, err := g.rpcInspectorFactory(
   329  		g.logger,
   330  		g.sporkId,
   331  		&g.gossipSubCfg.RpcInspector,
   332  		g.metricsCfg.Metrics,
   333  		g.metricsCfg.HeroCacheFactory,
   334  		g.networkType,
   335  		g.idProvider,
   336  		func() p2p.TopicProvider {
   337  			return gossipSub
   338  		},
   339  		consumer)
   340  	if err != nil {
   341  		return nil, fmt.Errorf("failed to create new rpc valiadation inspector: %w", err)
   342  	}
   343  	gossipSubConfigs.WithRpcInspector(rpcValidationInspector)
   344  
   345  	if g.gossipSubTracer != nil {
   346  		gossipSubConfigs.WithTracer(g.gossipSubTracer)
   347  	}
   348  
   349  	if g.h == nil {
   350  		return nil, fmt.Errorf("could not create gossipsub: host is nil")
   351  	}
   352  
   353  	gossipSub, err = g.gossipSubFactory(ctx, g.logger, g.h, gossipSubConfigs, rpcValidationInspector)
   354  	if err != nil {
   355  		return nil, fmt.Errorf("could not create gossipsub: %w", err)
   356  	}
   357  
   358  	return gossipSub, nil
   359  }