github.com/koko1123/flow-go-1@v0.29.6/network/p2p/p2pbuilder/libp2pNodeBuilder.go (about) 1 package p2pbuilder 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "net" 8 "time" 9 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/peer" 17 "github.com/libp2p/go-libp2p/core/routing" 18 "github.com/libp2p/go-libp2p/core/transport" 19 rcmgr "github.com/libp2p/go-libp2p/p2p/host/resource-manager" 20 "github.com/libp2p/go-libp2p/p2p/transport/tcp" 21 "github.com/multiformats/go-multiaddr" 22 madns "github.com/multiformats/go-multiaddr-dns" 23 "github.com/rs/zerolog" 24 25 "github.com/koko1123/flow-go-1/network/p2p" 26 "github.com/koko1123/flow-go-1/network/p2p/connection" 27 "github.com/koko1123/flow-go-1/network/p2p/p2pnode" 28 29 "github.com/koko1123/flow-go-1/network/p2p/subscription" 30 "github.com/koko1123/flow-go-1/network/p2p/utils" 31 32 "github.com/koko1123/flow-go-1/network/p2p/dht" 33 34 "github.com/koko1123/flow-go-1/model/flow" 35 "github.com/koko1123/flow-go-1/module" 36 "github.com/koko1123/flow-go-1/module/component" 37 "github.com/koko1123/flow-go-1/module/irrecoverable" 38 "github.com/koko1123/flow-go-1/network/p2p/keyutils" 39 "github.com/koko1123/flow-go-1/network/p2p/scoring" 40 "github.com/koko1123/flow-go-1/network/p2p/unicast" 41 fcrypto "github.com/onflow/flow-go/crypto" 42 ) 43 44 const ( 45 defaultMemoryLimitRatio = 0.2 // flow default 46 defaultFileDescriptorsRatio = 0.5 // libp2p default 47 ) 48 49 // LibP2PFactoryFunc is a factory function type for generating libp2p Node instances. 50 type LibP2PFactoryFunc func() (p2p.LibP2PNode, error) 51 type GossipSubFactoryFunc func(context.Context, zerolog.Logger, host.Host, p2p.PubSubAdapterConfig) (p2p.PubSubAdapter, error) 52 type CreateNodeFunc func(logger zerolog.Logger, 53 host host.Host, 54 pCache *p2pnode.ProtocolPeerCache, 55 uniMgr *unicast.Manager, 56 peerManager *connection.PeerManager) p2p.LibP2PNode 57 type GossipSubAdapterConfigFunc func(*p2p.BasePubSubAdapterConfig) p2p.PubSubAdapterConfig 58 59 // DefaultLibP2PNodeFactory returns a LibP2PFactoryFunc which generates the libp2p host initialized with the 60 // default options for the host, the pubsub and the ping service. 61 func DefaultLibP2PNodeFactory(log zerolog.Logger, 62 address string, 63 flowKey fcrypto.PrivateKey, 64 sporkId flow.Identifier, 65 idProvider module.IdentityProvider, 66 metrics module.NetworkMetrics, 67 resolver madns.BasicResolver, 68 peerScoringEnabled bool, 69 role string, 70 onInterceptPeerDialFilters, onInterceptSecuredFilters []p2p.PeerFilter, 71 connectionPruning bool, 72 updateInterval time.Duration, 73 rCfg *ResourceManagerConfig) LibP2PFactoryFunc { 74 return func() (p2p.LibP2PNode, error) { 75 builder := DefaultNodeBuilder(log, 76 address, 77 flowKey, 78 sporkId, 79 idProvider, 80 metrics, 81 resolver, 82 role, 83 onInterceptPeerDialFilters, 84 onInterceptSecuredFilters, 85 peerScoringEnabled, 86 connectionPruning, 87 updateInterval, 88 rCfg) 89 return builder.Build() 90 } 91 } 92 93 type NodeBuilder interface { 94 SetBasicResolver(madns.BasicResolver) NodeBuilder 95 SetSubscriptionFilter(pubsub.SubscriptionFilter) NodeBuilder 96 SetResourceManager(network.ResourceManager) NodeBuilder 97 SetConnectionManager(connmgr.ConnManager) NodeBuilder 98 SetConnectionGater(connmgr.ConnectionGater) NodeBuilder 99 SetRoutingSystem(func(context.Context, host.Host) (routing.Routing, error)) NodeBuilder 100 SetPeerManagerOptions(connectionPruning bool, updateInterval time.Duration) NodeBuilder 101 EnableGossipSubPeerScoring(provider module.IdentityProvider, ops ...scoring.PeerScoreParamsOption) NodeBuilder 102 SetCreateNode(CreateNodeFunc) NodeBuilder 103 SetGossipSubFactory(GossipSubFactoryFunc, GossipSubAdapterConfigFunc) NodeBuilder 104 Build() (p2p.LibP2PNode, error) 105 } 106 107 // ResourceManagerConfig returns the resource manager configuration for the libp2p node. 108 // The resource manager is used to limit the number of open connections and streams (as well as any other resources 109 // used by libp2p) for each peer. 110 type ResourceManagerConfig struct { 111 MemoryLimitRatio float64 // maximum allowed fraction of memory to be allocated by the libp2p resources in (0,1] 112 FileDescriptorsRatio float64 // maximum allowed fraction of file descriptors to be allocated by the libp2p resources in (0,1] 113 } 114 115 func DefaultResourceManagerConfig() *ResourceManagerConfig { 116 return &ResourceManagerConfig{ 117 MemoryLimitRatio: defaultMemoryLimitRatio, 118 FileDescriptorsRatio: defaultFileDescriptorsRatio, 119 } 120 } 121 122 type LibP2PNodeBuilder struct { 123 sporkID flow.Identifier 124 addr string 125 networkKey fcrypto.PrivateKey 126 logger zerolog.Logger 127 metrics module.LibP2PMetrics 128 basicResolver madns.BasicResolver 129 subscriptionFilter pubsub.SubscriptionFilter 130 resourceManager network.ResourceManager 131 resourceManagerCfg *ResourceManagerConfig 132 connManager connmgr.ConnManager 133 connGater connmgr.ConnectionGater 134 idProvider module.IdentityProvider 135 gossipSubFactory GossipSubFactoryFunc 136 gossipSubConfigFunc GossipSubAdapterConfigFunc 137 gossipSubPeerScoring bool // whether to enable gossipsub peer scoring 138 routingFactory func(context.Context, host.Host) (routing.Routing, error) 139 peerManagerEnablePruning bool 140 peerManagerUpdateInterval time.Duration 141 peerScoringParameterOptions []scoring.PeerScoreParamsOption 142 createNode CreateNodeFunc 143 } 144 145 func NewNodeBuilder(logger zerolog.Logger, 146 metrics module.LibP2PMetrics, 147 addr string, 148 networkKey fcrypto.PrivateKey, 149 sporkID flow.Identifier, 150 rCfg *ResourceManagerConfig) *LibP2PNodeBuilder { 151 return &LibP2PNodeBuilder{ 152 logger: logger, 153 sporkID: sporkID, 154 addr: addr, 155 networkKey: networkKey, 156 createNode: DefaultCreateNodeFunc, 157 gossipSubFactory: defaultGossipSubFactory(), 158 gossipSubConfigFunc: defaultGossipSubAdapterConfig(), 159 metrics: metrics, 160 resourceManagerCfg: rCfg, 161 } 162 } 163 164 func defaultGossipSubFactory() GossipSubFactoryFunc { 165 return func(ctx context.Context, logger zerolog.Logger, h host.Host, cfg p2p.PubSubAdapterConfig) (p2p.PubSubAdapter, error) { 166 return p2pnode.NewGossipSubAdapter(ctx, logger, h, cfg) 167 } 168 } 169 170 func defaultGossipSubAdapterConfig() GossipSubAdapterConfigFunc { 171 return func(cfg *p2p.BasePubSubAdapterConfig) p2p.PubSubAdapterConfig { 172 return p2pnode.NewGossipSubAdapterConfig(cfg) 173 } 174 175 } 176 177 // SetBasicResolver sets the DNS resolver for the node. 178 func (builder *LibP2PNodeBuilder) SetBasicResolver(br madns.BasicResolver) NodeBuilder { 179 builder.basicResolver = br 180 return builder 181 } 182 183 // SetSubscriptionFilter sets the pubsub subscription filter for the node. 184 func (builder *LibP2PNodeBuilder) SetSubscriptionFilter(filter pubsub.SubscriptionFilter) NodeBuilder { 185 builder.subscriptionFilter = filter 186 return builder 187 } 188 189 // SetResourceManager sets the resource manager for the node. 190 func (builder *LibP2PNodeBuilder) SetResourceManager(manager network.ResourceManager) NodeBuilder { 191 builder.resourceManager = manager 192 return builder 193 } 194 195 // SetConnectionManager sets the connection manager for the node. 196 func (builder *LibP2PNodeBuilder) SetConnectionManager(manager connmgr.ConnManager) NodeBuilder { 197 builder.connManager = manager 198 return builder 199 } 200 201 // SetConnectionGater sets the connection gater for the node. 202 func (builder *LibP2PNodeBuilder) SetConnectionGater(gater connmgr.ConnectionGater) NodeBuilder { 203 builder.connGater = gater 204 return builder 205 } 206 207 // SetRoutingSystem sets the routing factory function. 208 func (builder *LibP2PNodeBuilder) SetRoutingSystem(f func(context.Context, host.Host) (routing.Routing, error)) NodeBuilder { 209 builder.routingFactory = f 210 return builder 211 } 212 213 // EnableGossipSubPeerScoring sets builder.gossipSubPeerScoring to true. 214 func (builder *LibP2PNodeBuilder) EnableGossipSubPeerScoring(provider module.IdentityProvider, ops ...scoring.PeerScoreParamsOption) NodeBuilder { 215 builder.gossipSubPeerScoring = true 216 builder.idProvider = provider 217 builder.peerScoringParameterOptions = ops 218 return builder 219 } 220 221 // SetPeerManagerOptions sets the peer manager options. 222 func (builder *LibP2PNodeBuilder) SetPeerManagerOptions(connectionPruning bool, updateInterval time.Duration) NodeBuilder { 223 builder.peerManagerEnablePruning = connectionPruning 224 builder.peerManagerUpdateInterval = updateInterval 225 return builder 226 } 227 228 func (builder *LibP2PNodeBuilder) SetCreateNode(f CreateNodeFunc) NodeBuilder { 229 builder.createNode = f 230 return builder 231 } 232 233 func (builder *LibP2PNodeBuilder) SetGossipSubFactory(gf GossipSubFactoryFunc, cf GossipSubAdapterConfigFunc) NodeBuilder { 234 builder.gossipSubFactory = gf 235 builder.gossipSubConfigFunc = cf 236 return builder 237 } 238 239 // Build creates a new libp2p node using the configured options. 240 func (builder *LibP2PNodeBuilder) Build() (p2p.LibP2PNode, error) { 241 if builder.routingFactory == nil { 242 return nil, errors.New("routing factory is not set") 243 } 244 245 var opts []libp2p.Option 246 247 if builder.basicResolver != nil { 248 resolver, err := madns.NewResolver(madns.WithDefaultResolver(builder.basicResolver)) 249 250 if err != nil { 251 return nil, fmt.Errorf("could not create resolver: %w", err) 252 } 253 254 opts = append(opts, libp2p.MultiaddrResolver(resolver)) 255 } 256 257 if builder.resourceManager != nil { 258 opts = append(opts, libp2p.ResourceManager(builder.resourceManager)) 259 builder.logger.Warn(). 260 Msg("libp2p resource manager is overridden by the node builder, metrics may not be available") 261 } else { 262 // setting up default resource manager, by hooking in the resource manager metrics reporter. 263 limits := rcmgr.DefaultLimits 264 libp2p.SetDefaultServiceLimits(&limits) 265 266 mem, err := allowedMemory(builder.resourceManagerCfg.MemoryLimitRatio) 267 if err != nil { 268 return nil, fmt.Errorf("could not get allowed memory: %w", err) 269 } 270 fd, err := allowedFileDescriptors(builder.resourceManagerCfg.FileDescriptorsRatio) 271 if err != nil { 272 return nil, fmt.Errorf("could not get allowed file descriptors: %w", err) 273 } 274 275 mgr, err := rcmgr.NewResourceManager(rcmgr.NewFixedLimiter(limits.Scale(mem, fd)), rcmgr.WithMetrics(builder.metrics)) 276 if err != nil { 277 return nil, fmt.Errorf("could not create libp2p resource manager: %w", err) 278 } 279 opts = append(opts, libp2p.ResourceManager(mgr)) 280 builder.logger.Info().Msg("libp2p resource manager is set to default with metrics") 281 } 282 283 if builder.connManager != nil { 284 opts = append(opts, libp2p.ConnectionManager(builder.connManager)) 285 } 286 287 if builder.connGater != nil { 288 opts = append(opts, libp2p.ConnectionGater(builder.connGater)) 289 } 290 291 h, err := DefaultLibP2PHost(builder.addr, builder.networkKey, opts...) 292 293 if err != nil { 294 return nil, err 295 } 296 297 pCache, err := p2pnode.NewProtocolPeerCache(builder.logger, h) 298 if err != nil { 299 return nil, err 300 } 301 302 unicastManager := unicast.NewUnicastManager(builder.logger, unicast.NewLibP2PStreamFactory(h), builder.sporkID) 303 304 var peerManager *connection.PeerManager 305 if builder.peerManagerUpdateInterval > 0 { 306 connector, err := connection.NewLibp2pConnector(builder.logger, h, builder.peerManagerEnablePruning) 307 if err != nil { 308 return nil, fmt.Errorf("failed to create libp2p connector: %w", err) 309 } 310 311 peerManager = connection.NewPeerManager(builder.logger, builder.peerManagerUpdateInterval, connector) 312 } 313 314 node := builder.createNode(builder.logger, h, pCache, unicastManager, peerManager) 315 316 cm := component.NewComponentManagerBuilder(). 317 AddWorker(func(ctx irrecoverable.SignalerContext, ready component.ReadyFunc) { 318 rsys, err := builder.routingFactory(ctx, h) 319 if err != nil { 320 ctx.Throw(fmt.Errorf("could not create libp2p node routing: %w", err)) 321 } 322 323 node.SetRouting(rsys) 324 325 gossipSubConfigs := builder.gossipSubConfigFunc(&p2p.BasePubSubAdapterConfig{ 326 MaxMessageSize: p2pnode.DefaultMaxPubSubMsgSize, 327 }) 328 gossipSubConfigs.WithMessageIdFunction(utils.MessageID) 329 gossipSubConfigs.WithRoutingDiscovery(rsys) 330 if builder.subscriptionFilter != nil { 331 gossipSubConfigs.WithSubscriptionFilter(builder.subscriptionFilter) 332 } 333 334 var scoreOpt *scoring.ScoreOption 335 if builder.gossipSubPeerScoring { 336 scoreOpt = scoring.NewScoreOption(builder.logger, builder.idProvider, builder.peerScoringParameterOptions...) 337 gossipSubConfigs.WithScoreOption(scoreOpt) 338 } 339 340 // The app-specific rpc inspector is a hook into the pubsub that is invoked upon receiving any incoming RPC. 341 gossipSubMetrics := p2pnode.NewGossipSubControlMessageMetrics(builder.metrics, builder.logger) 342 gossipSubConfigs.WithAppSpecificRpcInspector(func(from peer.ID, rpc *pubsub.RPC) error { 343 gossipSubMetrics.ObserveRPC(from, rpc) 344 return nil 345 }) 346 347 // builds GossipSub with the given factory 348 gossipSub, err := builder.gossipSubFactory(ctx, builder.logger, h, gossipSubConfigs) 349 if err != nil { 350 ctx.Throw(fmt.Errorf("could not create gossipsub: %w", err)) 351 } 352 353 if scoreOpt != nil { 354 scoreOpt.SetSubscriptionProvider(scoring.NewSubscriptionProvider(builder.logger, gossipSub)) 355 } 356 node.SetPubSub(gossipSub) 357 358 ready() 359 <-ctx.Done() 360 361 err = node.Stop() 362 if err != nil { 363 // ignore context cancellation errors 364 if !errors.Is(err, context.Canceled) && !errors.Is(err, context.DeadlineExceeded) { 365 ctx.Throw(fmt.Errorf("could not stop libp2p node: %w", err)) 366 } 367 } 368 }). 369 Build() 370 371 node.SetComponentManager(cm) 372 373 return node, nil 374 } 375 376 // DefaultLibP2PHost returns a libp2p host initialized to listen on the given address and using the given private key and 377 // customized with options 378 func DefaultLibP2PHost(address string, key fcrypto.PrivateKey, options ...config.Option) (host.Host, error) { 379 defaultOptions, err := defaultLibP2POptions(address, key) 380 if err != nil { 381 return nil, err 382 } 383 384 allOptions := append(defaultOptions, options...) 385 386 // create the libp2p host 387 libP2PHost, err := libp2p.New(allOptions...) 388 if err != nil { 389 return nil, fmt.Errorf("could not create libp2p host: %w", err) 390 } 391 392 return libP2PHost, nil 393 } 394 395 // defaultLibP2POptions creates and returns the standard LibP2P host options that are used for the Flow Libp2p network 396 func defaultLibP2POptions(address string, key fcrypto.PrivateKey) ([]config.Option, error) { 397 398 libp2pKey, err := keyutils.LibP2PPrivKeyFromFlow(key) 399 if err != nil { 400 return nil, fmt.Errorf("could not generate libp2p key: %w", err) 401 } 402 403 ip, port, err := net.SplitHostPort(address) 404 if err != nil { 405 return nil, fmt.Errorf("could not split node address %s:%w", address, err) 406 } 407 408 sourceMultiAddr, err := multiaddr.NewMultiaddr(utils.MultiAddressStr(ip, port)) 409 if err != nil { 410 return nil, fmt.Errorf("failed to translate Flow address to Libp2p multiaddress: %w", err) 411 } 412 413 // create a transport which disables port reuse and web socket. 414 // Port reuse enables listening and dialing from the same TCP port (https://github.com/libp2p/go-reuseport) 415 // While this sounds great, it intermittently causes a 'broken pipe' error 416 // as the 1-k discovery process and the 1-1 messaging both sometimes attempt to open connection to the same target 417 // As of now there is no requirement of client sockets to be a well-known port, so disabling port reuse all together. 418 t := libp2p.Transport(func(u transport.Upgrader) (*tcp.TcpTransport, error) { 419 return tcp.NewTCPTransport(u, nil, tcp.DisableReuseport()) 420 }) 421 422 // gather all the options for the libp2p node 423 options := []config.Option{ 424 libp2p.ListenAddrs(sourceMultiAddr), // set the listen address 425 libp2p.Identity(libp2pKey), // pass in the networking key 426 t, // set the transport 427 } 428 429 return options, nil 430 } 431 432 // DefaultCreateNodeFunc returns new libP2P node. 433 func DefaultCreateNodeFunc(logger zerolog.Logger, 434 host host.Host, 435 pCache *p2pnode.ProtocolPeerCache, 436 uniMgr *unicast.Manager, 437 peerManager *connection.PeerManager) p2p.LibP2PNode { 438 return p2pnode.NewNode(logger, host, pCache, uniMgr, peerManager) 439 } 440 441 // DefaultNodeBuilder returns a node builder. 442 func DefaultNodeBuilder(log zerolog.Logger, 443 address string, 444 flowKey fcrypto.PrivateKey, 445 sporkId flow.Identifier, 446 idProvider module.IdentityProvider, 447 metrics module.LibP2PMetrics, 448 resolver madns.BasicResolver, 449 role string, 450 onInterceptPeerDialFilters, onInterceptSecuredFilters []p2p.PeerFilter, 451 peerScoringEnabled bool, 452 connectionPruning bool, 453 updateInterval time.Duration, 454 rCfg *ResourceManagerConfig) NodeBuilder { 455 connManager := connection.NewConnManager(log, metrics) 456 457 // set the default connection gater peer filters for both InterceptPeerDial and InterceptSecured callbacks 458 peerFilter := notEjectedPeerFilter(idProvider) 459 peerFilters := []p2p.PeerFilter{peerFilter} 460 461 connGater := connection.NewConnGater(log, 462 connection.WithOnInterceptPeerDialFilters(append(peerFilters, onInterceptPeerDialFilters...)), 463 connection.WithOnInterceptSecuredFilters(append(peerFilters, onInterceptSecuredFilters...))) 464 465 builder := NewNodeBuilder(log, metrics, address, flowKey, sporkId, rCfg). 466 SetBasicResolver(resolver). 467 SetConnectionManager(connManager). 468 SetConnectionGater(connGater). 469 SetRoutingSystem(func(ctx context.Context, host host.Host) (routing.Routing, error) { 470 return dht.NewDHT(ctx, host, unicast.FlowDHTProtocolID(sporkId), log, metrics, dht.AsServer()) 471 }). 472 SetPeerManagerOptions(connectionPruning, updateInterval). 473 SetCreateNode(DefaultCreateNodeFunc) 474 475 if peerScoringEnabled { 476 builder.EnableGossipSubPeerScoring(idProvider) 477 } 478 479 if role != "ghost" { 480 r, _ := flow.ParseRole(role) 481 builder.SetSubscriptionFilter(subscription.NewRoleBasedFilter(r, idProvider)) 482 } 483 484 return builder 485 }