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 }