github.com/onflow/flow-go@v0.33.17/network/p2p/builder/libp2pNodeBuilder.go (about) 1 package p2pbuilder 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "net" 8 9 "github.com/libp2p/go-libp2p" 10 pubsub "github.com/libp2p/go-libp2p-pubsub" 11 "github.com/libp2p/go-libp2p/config" 12 "github.com/libp2p/go-libp2p/core/connmgr" 13 "github.com/libp2p/go-libp2p/core/host" 14 "github.com/libp2p/go-libp2p/core/network" 15 "github.com/libp2p/go-libp2p/core/routing" 16 "github.com/libp2p/go-libp2p/core/transport" 17 rcmgr "github.com/libp2p/go-libp2p/p2p/host/resource-manager" 18 "github.com/libp2p/go-libp2p/p2p/transport/tcp" 19 "github.com/multiformats/go-multiaddr" 20 madns "github.com/multiformats/go-multiaddr-dns" 21 "github.com/rs/zerolog" 22 23 fcrypto "github.com/onflow/flow-go/crypto" 24 "github.com/onflow/flow-go/model/flow" 25 "github.com/onflow/flow-go/module" 26 "github.com/onflow/flow-go/module/component" 27 "github.com/onflow/flow-go/module/irrecoverable" 28 "github.com/onflow/flow-go/module/metrics" 29 flownet "github.com/onflow/flow-go/network" 30 "github.com/onflow/flow-go/network/netconf" 31 "github.com/onflow/flow-go/network/p2p" 32 p2pbuilderconfig "github.com/onflow/flow-go/network/p2p/builder/config" 33 gossipsubbuilder "github.com/onflow/flow-go/network/p2p/builder/gossipsub" 34 p2pconfig "github.com/onflow/flow-go/network/p2p/config" 35 "github.com/onflow/flow-go/network/p2p/connection" 36 "github.com/onflow/flow-go/network/p2p/dht" 37 "github.com/onflow/flow-go/network/p2p/keyutils" 38 p2plogging "github.com/onflow/flow-go/network/p2p/logging" 39 p2pnode "github.com/onflow/flow-go/network/p2p/node" 40 "github.com/onflow/flow-go/network/p2p/subscription" 41 "github.com/onflow/flow-go/network/p2p/unicast" 42 unicastcache "github.com/onflow/flow-go/network/p2p/unicast/cache" 43 "github.com/onflow/flow-go/network/p2p/unicast/protocols" 44 "github.com/onflow/flow-go/network/p2p/unicast/stream" 45 "github.com/onflow/flow-go/network/p2p/utils" 46 ) 47 48 type DhtSystemActivation bool 49 50 const ( 51 DhtSystemEnabled DhtSystemActivation = true 52 DhtSystemDisabled DhtSystemActivation = false 53 ) 54 55 type LibP2PNodeBuilder struct { 56 gossipSubBuilder p2p.GossipSubBuilder 57 sporkId flow.Identifier 58 address string 59 networkKey fcrypto.PrivateKey 60 logger zerolog.Logger 61 metricsConfig *p2pbuilderconfig.MetricsConfig 62 basicResolver madns.BasicResolver 63 64 resourceManager network.ResourceManager 65 resourceManagerCfg *p2pconfig.ResourceManagerConfig 66 connManager connmgr.ConnManager 67 connGater p2p.ConnectionGater 68 routingFactory func(context.Context, host.Host) (routing.Routing, error) 69 peerManagerConfig *p2pbuilderconfig.PeerManagerConfig 70 createNode p2p.NodeConstructor 71 disallowListCacheCfg *p2p.DisallowListCacheConfig 72 unicastConfig *p2pbuilderconfig.UnicastConfig 73 networkingType flownet.NetworkingType // whether the node is running in private (staked) or public (unstaked) network 74 } 75 76 func NewNodeBuilder( 77 logger zerolog.Logger, 78 gossipSubCfg *p2pconfig.GossipSubParameters, 79 metricsConfig *p2pbuilderconfig.MetricsConfig, 80 networkingType flownet.NetworkingType, 81 address string, 82 networkKey fcrypto.PrivateKey, 83 sporkId flow.Identifier, 84 idProvider module.IdentityProvider, 85 rCfg *p2pconfig.ResourceManagerConfig, 86 peerManagerConfig *p2pbuilderconfig.PeerManagerConfig, 87 disallowListCacheCfg *p2p.DisallowListCacheConfig, 88 unicastConfig *p2pbuilderconfig.UnicastConfig, 89 ) *LibP2PNodeBuilder { 90 return &LibP2PNodeBuilder{ 91 logger: logger, 92 sporkId: sporkId, 93 address: address, 94 networkKey: networkKey, 95 createNode: func(cfg *p2p.NodeConfig) (p2p.LibP2PNode, error) { return p2pnode.NewNode(cfg) }, 96 metricsConfig: metricsConfig, 97 resourceManagerCfg: rCfg, 98 disallowListCacheCfg: disallowListCacheCfg, 99 networkingType: networkingType, 100 gossipSubBuilder: gossipsubbuilder.NewGossipSubBuilder(logger, 101 metricsConfig, 102 gossipSubCfg, 103 networkingType, 104 sporkId, 105 idProvider), 106 peerManagerConfig: peerManagerConfig, 107 unicastConfig: unicastConfig, 108 } 109 } 110 111 var _ p2p.NodeBuilder = &LibP2PNodeBuilder{} 112 113 // SetBasicResolver sets the DNS resolver for the node. 114 func (builder *LibP2PNodeBuilder) SetBasicResolver(br madns.BasicResolver) p2p.NodeBuilder { 115 builder.basicResolver = br 116 return builder 117 } 118 119 // SetSubscriptionFilter sets the pubsub subscription filter for the node. 120 func (builder *LibP2PNodeBuilder) SetSubscriptionFilter(filter pubsub.SubscriptionFilter) p2p.NodeBuilder { 121 builder.gossipSubBuilder.SetSubscriptionFilter(filter) 122 return builder 123 } 124 125 // SetResourceManager sets the resource manager for the node. 126 func (builder *LibP2PNodeBuilder) SetResourceManager(manager network.ResourceManager) p2p.NodeBuilder { 127 builder.resourceManager = manager 128 return builder 129 } 130 131 // SetConnectionManager sets the connection manager for the node. 132 func (builder *LibP2PNodeBuilder) SetConnectionManager(manager connmgr.ConnManager) p2p.NodeBuilder { 133 builder.connManager = manager 134 return builder 135 } 136 137 // SetConnectionGater sets the connection gater for the node. 138 func (builder *LibP2PNodeBuilder) SetConnectionGater(gater p2p.ConnectionGater) p2p.NodeBuilder { 139 builder.connGater = gater 140 return builder 141 } 142 143 // SetRoutingSystem sets the routing system factory function. 144 func (builder *LibP2PNodeBuilder) SetRoutingSystem(f func(context.Context, host.Host) (routing.Routing, error)) p2p.NodeBuilder { 145 builder.routingFactory = f 146 return builder 147 } 148 149 func (builder *LibP2PNodeBuilder) SetGossipSubFactory(gf p2p.GossipSubFactoryFunc, cf p2p.GossipSubAdapterConfigFunc) p2p.NodeBuilder { 150 builder.gossipSubBuilder.SetGossipSubFactory(gf) 151 builder.gossipSubBuilder.SetGossipSubConfigFunc(cf) 152 return builder 153 } 154 155 // OverrideGossipSubScoringConfig overrides the default peer scoring config for the GossipSub protocol. 156 // Note that it does not enable peer scoring. The peer scoring is enabled directly by setting the `peer-scoring-enabled` flag to true in `default-config.yaml`, or 157 // by setting the `gossipsub-peer-scoring-enabled` runtime flag to true. This function only overrides the default peer scoring config which takes effect 158 // only if the peer scoring is enabled (mostly for testing purposes). 159 // Any existing peer scoring config attribute that is set in the override will override the default peer scoring config. 160 // Anything that is left to nil or zero value in the override will be ignored and the default value will be used. 161 // Note: it is not recommended to override the default peer scoring config in production unless you know what you are doing. 162 // Args: 163 // - PeerScoringConfigOverride: override for the peer scoring config- Recommended to use PeerScoringConfigNoOverride for production. 164 // Returns: 165 // none 166 func (builder *LibP2PNodeBuilder) OverrideGossipSubScoringConfig(config *p2p.PeerScoringConfigOverride) p2p.NodeBuilder { 167 builder.gossipSubBuilder.EnableGossipSubScoringWithOverride(config) 168 return builder 169 } 170 171 // OverrideNodeConstructor overrides the default node constructor, i.e., the function that creates a new libp2p node. 172 // The purpose of override is to allow the node to provide a custom node constructor for sake of testing or experimentation. 173 // It is NOT recommended to override the default node constructor in production unless you know what you are doing. 174 // Args: 175 // - NodeConstructor: custom node constructor 176 // Returns: 177 // none 178 func (builder *LibP2PNodeBuilder) OverrideNodeConstructor(f p2p.NodeConstructor) p2p.NodeBuilder { 179 builder.createNode = f 180 return builder 181 } 182 183 func (builder *LibP2PNodeBuilder) OverrideDefaultRpcInspectorSuiteFactory(factory p2p.GossipSubRpcInspectorSuiteFactoryFunc) p2p.NodeBuilder { 184 builder.gossipSubBuilder.OverrideDefaultRpcInspectorSuiteFactory(factory) 185 return builder 186 } 187 188 // Build creates a new libp2p node using the configured options. 189 func (builder *LibP2PNodeBuilder) Build() (p2p.LibP2PNode, error) { 190 var opts []libp2p.Option 191 192 if builder.basicResolver != nil { 193 resolver, err := madns.NewResolver(madns.WithDefaultResolver(builder.basicResolver)) 194 195 if err != nil { 196 return nil, fmt.Errorf("could not create resolver: %w", err) 197 } 198 199 opts = append(opts, libp2p.MultiaddrResolver(resolver)) 200 } 201 202 if builder.resourceManager != nil { 203 opts = append(opts, libp2p.ResourceManager(builder.resourceManager)) 204 builder.logger.Warn(). 205 Msg("libp2p resource manager is overridden by the node builder, metrics may not be available") 206 } else { 207 // scales the default limits by the allowed memory and file descriptors and applies the inbound connection and stream limits. 208 limits, err := BuildLibp2pResourceManagerLimits(builder.logger, builder.resourceManagerCfg) 209 if err != nil { 210 return nil, fmt.Errorf("could not build libp2p resource manager limits: %w", err) 211 } 212 mgr, err := rcmgr.NewResourceManager(rcmgr.NewFixedLimiter(*limits), rcmgr.WithMetrics(builder.metricsConfig.Metrics)) 213 if err != nil { 214 return nil, fmt.Errorf("could not create libp2p resource manager: %w", err) 215 } 216 217 opts = append(opts, libp2p.ResourceManager(mgr)) 218 builder.logger.Info().Msgf("default libp2p resource manager is enabled with metrics, pubkey: %s", builder.networkKey.PublicKey()) 219 } 220 221 if builder.connManager != nil { 222 opts = append(opts, libp2p.ConnectionManager(builder.connManager)) 223 } 224 225 if builder.connGater != nil { 226 opts = append(opts, libp2p.ConnectionGater(builder.connGater)) 227 } 228 229 h, err := DefaultLibP2PHost(builder.address, builder.networkKey, opts...) 230 if err != nil { 231 return nil, err 232 } 233 builder.gossipSubBuilder.SetHost(h) 234 builder.logger = builder.logger.With().Str("local_peer_id", p2plogging.PeerId(h.ID())).Logger() 235 236 var peerManager p2p.PeerManager 237 if builder.peerManagerConfig.UpdateInterval > 0 { 238 connector, err := builder.peerManagerConfig.ConnectorFactory(h) 239 if err != nil { 240 return nil, fmt.Errorf("failed to create libp2p connector: %w", err) 241 } 242 peerUpdater, err := connection.NewPeerUpdater( 243 &connection.PeerUpdaterConfig{ 244 PruneConnections: builder.peerManagerConfig.ConnectionPruning, 245 Logger: builder.logger, 246 Host: connection.NewConnectorHost(h), 247 Connector: connector, 248 }) 249 if err != nil { 250 return nil, fmt.Errorf("failed to create libp2p connector: %w", err) 251 } 252 253 peerManager = connection.NewPeerManager(builder.logger, builder.peerManagerConfig.UpdateInterval, peerUpdater) 254 255 if builder.unicastConfig.RateLimiterDistributor != nil { 256 builder.unicastConfig.RateLimiterDistributor.AddConsumer(peerManager) 257 } 258 } 259 260 node, err := builder.createNode(&p2p.NodeConfig{ 261 Parameters: &p2p.NodeParameters{ 262 EnableProtectedStreams: builder.unicastConfig.EnableStreamProtection, 263 }, 264 Logger: builder.logger, 265 Host: h, 266 PeerManager: peerManager, 267 DisallowListCacheCfg: builder.disallowListCacheCfg, 268 }) 269 if err != nil { 270 return nil, fmt.Errorf("could not create libp2p node: %w", err) 271 } 272 273 if builder.connGater != nil { 274 builder.connGater.SetDisallowListOracle(node) 275 } 276 277 unicastManager, err := unicast.NewUnicastManager(&unicast.ManagerConfig{ 278 Logger: builder.logger, 279 StreamFactory: stream.NewLibP2PStreamFactory(h), 280 SporkId: builder.sporkId, 281 Metrics: builder.metricsConfig.Metrics, 282 Parameters: &builder.unicastConfig.UnicastManager, 283 UnicastConfigCacheFactory: func(configFactory func() unicast.Config) unicast.ConfigCache { 284 return unicastcache.NewUnicastConfigCache(builder.unicastConfig.UnicastManager.ConfigCacheSize, builder.logger, 285 metrics.DialConfigCacheMetricFactory(builder.metricsConfig.HeroCacheFactory, builder.networkingType), 286 configFactory) 287 }, 288 }) 289 if err != nil { 290 return nil, fmt.Errorf("could not create unicast manager: %w", err) 291 } 292 node.SetUnicastManager(unicastManager) 293 294 cm := component.NewComponentManagerBuilder(). 295 AddWorker(func(ctx irrecoverable.SignalerContext, ready component.ReadyFunc) { 296 if builder.routingFactory != nil { 297 routingSystem, err := builder.routingFactory(ctx, h) 298 if err != nil { 299 ctx.Throw(fmt.Errorf("could not create routing system: %w", err)) 300 } 301 if err := node.SetRouting(routingSystem); err != nil { 302 ctx.Throw(fmt.Errorf("could not set routing system: %w", err)) 303 } 304 builder.gossipSubBuilder.SetRoutingSystem(routingSystem) 305 builder.logger.Debug().Msg("routing system created") 306 } 307 // gossipsub is created here, because it needs to be created during the node startup. 308 gossipSub, err := builder.gossipSubBuilder.Build(ctx) 309 if err != nil { 310 ctx.Throw(fmt.Errorf("could not create gossipsub: %w", err)) 311 } 312 node.SetPubSub(gossipSub) 313 gossipSub.Start(ctx) 314 ready() 315 316 <-gossipSub.Done() 317 }). 318 AddWorker(func(ctx irrecoverable.SignalerContext, ready component.ReadyFunc) { 319 // encapsulates shutdown logic for the libp2p node. 320 ready() 321 <-ctx.Done() 322 // we wait till the context is done, and then we stop the libp2p node. 323 324 err = node.Stop() 325 if err != nil { 326 // ignore context cancellation errors 327 if !errors.Is(err, context.Canceled) && !errors.Is(err, context.DeadlineExceeded) { 328 ctx.Throw(fmt.Errorf("could not stop libp2p node: %w", err)) 329 } 330 } 331 }) 332 333 node.SetComponentManager(cm.Build()) 334 335 return node, nil 336 } 337 338 // DefaultLibP2PHost returns a libp2p host initialized to listen on the given address and using the given private key and 339 // customized with options 340 func DefaultLibP2PHost(address string, key fcrypto.PrivateKey, options ...config.Option) (host.Host, error) { 341 defaultOptions, err := defaultLibP2POptions(address, key) 342 if err != nil { 343 return nil, err 344 } 345 346 allOptions := append(defaultOptions, options...) 347 348 // create the libp2p host 349 libP2PHost, err := libp2p.New(allOptions...) 350 if err != nil { 351 return nil, fmt.Errorf("could not create libp2p host: %w", err) 352 } 353 354 return libP2PHost, nil 355 } 356 357 // defaultLibP2POptions creates and returns the standard LibP2P host options that are used for the Flow Libp2p network 358 func defaultLibP2POptions(address string, key fcrypto.PrivateKey) ([]config.Option, error) { 359 360 libp2pKey, err := keyutils.LibP2PPrivKeyFromFlow(key) 361 if err != nil { 362 return nil, fmt.Errorf("could not generate libp2p key: %w", err) 363 } 364 365 ip, port, err := net.SplitHostPort(address) 366 if err != nil { 367 return nil, fmt.Errorf("could not split node address %s:%w", address, err) 368 } 369 370 sourceMultiAddr, err := multiaddr.NewMultiaddr(utils.MultiAddressStr(ip, port)) 371 if err != nil { 372 return nil, fmt.Errorf("failed to translate Flow address to Libp2p multiaddress: %w", err) 373 } 374 375 // create a transport which disables port reuse and web socket. 376 // Port reuse enables listening and dialing from the same TCP port (https://github.com/libp2p/go-reuseport) 377 // While this sounds great, it intermittently causes a 'broken pipe' error 378 // as the 1-k discovery process and the 1-1 messaging both sometimes attempt to open connection to the same target 379 // As of now there is no requirement of client sockets to be a well-known port, so disabling port reuse all together. 380 t := libp2p.Transport(func(u transport.Upgrader) (*tcp.TcpTransport, error) { 381 return tcp.NewTCPTransport(u, nil, tcp.DisableReuseport()) 382 }) 383 384 // gather all the options for the libp2p node 385 options := []config.Option{ 386 libp2p.ListenAddrs(sourceMultiAddr), // set the listen address 387 libp2p.Identity(libp2pKey), // pass in the networking key 388 t, // set the transport 389 } 390 391 return options, nil 392 } 393 394 // DefaultNodeBuilder returns a node builder. 395 func DefaultNodeBuilder( 396 logger zerolog.Logger, 397 address string, 398 networkingType flownet.NetworkingType, 399 flowKey fcrypto.PrivateKey, 400 sporkId flow.Identifier, 401 idProvider module.IdentityProvider, 402 metricsCfg *p2pbuilderconfig.MetricsConfig, 403 resolver madns.BasicResolver, 404 role string, 405 connGaterCfg *p2pbuilderconfig.ConnectionGaterConfig, 406 peerManagerCfg *p2pbuilderconfig.PeerManagerConfig, 407 gossipCfg *p2pconfig.GossipSubParameters, 408 rCfg *p2pconfig.ResourceManagerConfig, 409 uniCfg *p2pbuilderconfig.UnicastConfig, 410 connMgrConfig *netconf.ConnectionManager, 411 disallowListCacheCfg *p2p.DisallowListCacheConfig, 412 dhtSystemActivation DhtSystemActivation, 413 ) (p2p.NodeBuilder, error) { 414 415 connManager, err := connection.NewConnManager(logger, metricsCfg.Metrics, connMgrConfig) 416 if err != nil { 417 return nil, fmt.Errorf("could not create connection manager: %w", err) 418 } 419 420 // set the default connection gater peer filters for both InterceptPeerDial and InterceptSecured callbacks 421 peerFilter := notEjectedPeerFilter(idProvider) 422 peerFilters := []p2p.PeerFilter{peerFilter} 423 424 connGater := connection.NewConnGater( 425 logger, 426 idProvider, 427 connection.WithOnInterceptPeerDialFilters(append(peerFilters, connGaterCfg.InterceptPeerDialFilters...)), 428 connection.WithOnInterceptSecuredFilters(append(peerFilters, connGaterCfg.InterceptSecuredFilters...))) 429 430 builder := NewNodeBuilder(logger, 431 gossipCfg, 432 metricsCfg, 433 networkingType, 434 address, 435 flowKey, 436 sporkId, 437 idProvider, 438 rCfg, peerManagerCfg, 439 disallowListCacheCfg, 440 uniCfg). 441 SetBasicResolver(resolver). 442 SetConnectionManager(connManager). 443 SetConnectionGater(connGater) 444 445 if role != "ghost" { 446 r, err := flow.ParseRole(role) 447 if err != nil { 448 return nil, fmt.Errorf("could not parse role: %w", err) 449 } 450 builder.SetSubscriptionFilter(subscription.NewRoleBasedFilter(r, idProvider)) 451 452 if dhtSystemActivation == DhtSystemEnabled { 453 builder.SetRoutingSystem( 454 func(ctx context.Context, host host.Host) (routing.Routing, error) { 455 return dht.NewDHT(ctx, host, protocols.FlowDHTProtocolID(sporkId), logger, metricsCfg.Metrics, dht.AsServer()) 456 }) 457 } 458 } 459 460 return builder, nil 461 }