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 }