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  }