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