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  }