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  }