github.com/prysmaticlabs/prysm@v1.4.4/beacon-chain/p2p/service.go (about)

     1  // Package p2p defines the network protocol implementation for Ethereum consensus
     2  // used by beacon nodes, including peer discovery using discv5, gossip-sub
     3  // using libp2p, and handing peer lifecycles + handshakes.
     4  package p2p
     5  
     6  import (
     7  	"context"
     8  	"crypto/ecdsa"
     9  	"sync"
    10  	"time"
    11  
    12  	"github.com/ethereum/go-ethereum/p2p/enode"
    13  	"github.com/ethereum/go-ethereum/p2p/enr"
    14  	"github.com/kevinms/leakybucket-go"
    15  	"github.com/libp2p/go-libp2p"
    16  	"github.com/libp2p/go-libp2p-core/host"
    17  	"github.com/libp2p/go-libp2p-core/network"
    18  	"github.com/libp2p/go-libp2p-core/peer"
    19  	"github.com/libp2p/go-libp2p-core/protocol"
    20  	pubsub "github.com/libp2p/go-libp2p-pubsub"
    21  	"github.com/libp2p/go-libp2p/p2p/protocol/identify"
    22  	"github.com/multiformats/go-multiaddr"
    23  	"github.com/pkg/errors"
    24  	"github.com/prysmaticlabs/prysm/beacon-chain/core/feed"
    25  	statefeed "github.com/prysmaticlabs/prysm/beacon-chain/core/feed/state"
    26  	"github.com/prysmaticlabs/prysm/beacon-chain/p2p/encoder"
    27  	"github.com/prysmaticlabs/prysm/beacon-chain/p2p/peers"
    28  	"github.com/prysmaticlabs/prysm/beacon-chain/p2p/peers/scorers"
    29  	"github.com/prysmaticlabs/prysm/shared"
    30  	"github.com/prysmaticlabs/prysm/shared/interfaces"
    31  	"github.com/prysmaticlabs/prysm/shared/params"
    32  	"github.com/prysmaticlabs/prysm/shared/runutil"
    33  	"github.com/prysmaticlabs/prysm/shared/slotutil"
    34  	"github.com/sirupsen/logrus"
    35  	"go.opencensus.io/trace"
    36  )
    37  
    38  var _ shared.Service = (*Service)(nil)
    39  
    40  // In the event that we are at our peer limit, we
    41  // stop looking for new peers and instead poll
    42  // for the current peer limit status for the time period
    43  // defined below.
    44  var pollingPeriod = 6 * time.Second
    45  
    46  // Refresh rate of ENR set at twice per slot.
    47  var refreshRate = slotutil.DivideSlotBy(2)
    48  
    49  // maxBadResponses is the maximum number of bad responses from a peer before we stop talking to it.
    50  const maxBadResponses = 5
    51  
    52  // maxDialTimeout is the timeout for a single peer dial.
    53  var maxDialTimeout = params.BeaconNetworkConfig().RespTimeout
    54  
    55  // Service for managing peer to peer (p2p) networking.
    56  type Service struct {
    57  	started               bool
    58  	isPreGenesis          bool
    59  	currentForkDigest     [4]byte
    60  	pingMethod            func(ctx context.Context, id peer.ID) error
    61  	cancel                context.CancelFunc
    62  	cfg                   *Config
    63  	peers                 *peers.Status
    64  	addrFilter            *multiaddr.Filters
    65  	ipLimiter             *leakybucket.Collector
    66  	privKey               *ecdsa.PrivateKey
    67  	metaData              interfaces.Metadata
    68  	pubsub                *pubsub.PubSub
    69  	joinedTopics          map[string]*pubsub.Topic
    70  	joinedTopicsLock      sync.Mutex
    71  	subnetsLock           map[uint64]*sync.RWMutex
    72  	subnetsLockLock       sync.Mutex // Lock access to subnetsLock
    73  	initializationLock    sync.Mutex
    74  	dv5Listener           Listener
    75  	startupErr            error
    76  	stateNotifier         statefeed.Notifier
    77  	ctx                   context.Context
    78  	host                  host.Host
    79  	genesisTime           time.Time
    80  	genesisValidatorsRoot []byte
    81  	activeValidatorCount  uint64
    82  }
    83  
    84  // NewService initializes a new p2p service compatible with shared.Service interface. No
    85  // connections are made until the Start function is called during the service registry startup.
    86  func NewService(ctx context.Context, cfg *Config) (*Service, error) {
    87  	var err error
    88  	ctx, cancel := context.WithCancel(ctx)
    89  	_ = cancel // govet fix for lost cancel. Cancel is handled in service.Stop().
    90  
    91  	s := &Service{
    92  		ctx:           ctx,
    93  		stateNotifier: cfg.StateNotifier,
    94  		cancel:        cancel,
    95  		cfg:           cfg,
    96  		isPreGenesis:  true,
    97  		joinedTopics:  make(map[string]*pubsub.Topic, len(GossipTopicMappings)),
    98  		subnetsLock:   make(map[uint64]*sync.RWMutex),
    99  	}
   100  
   101  	dv5Nodes := parseBootStrapAddrs(s.cfg.BootstrapNodeAddr)
   102  
   103  	cfg.Discv5BootStrapAddr = dv5Nodes
   104  
   105  	ipAddr := ipAddr()
   106  	s.privKey, err = privKey(s.cfg)
   107  	if err != nil {
   108  		log.WithError(err).Error("Failed to generate p2p private key")
   109  		return nil, err
   110  	}
   111  	s.metaData, err = metaDataFromConfig(s.cfg)
   112  	if err != nil {
   113  		log.WithError(err).Error("Failed to create peer metadata")
   114  		return nil, err
   115  	}
   116  	s.addrFilter, err = configureFilter(s.cfg)
   117  	if err != nil {
   118  		log.WithError(err).Error("Failed to create address filter")
   119  		return nil, err
   120  	}
   121  	s.ipLimiter = leakybucket.NewCollector(ipLimit, ipBurst, true /* deleteEmptyBuckets */)
   122  
   123  	opts := s.buildOptions(ipAddr, s.privKey)
   124  	h, err := libp2p.New(s.ctx, opts...)
   125  	if err != nil {
   126  		log.WithError(err).Error("Failed to create p2p host")
   127  		return nil, err
   128  	}
   129  
   130  	s.host = h
   131  	s.host.RemoveStreamHandler(identify.IDDelta)
   132  
   133  	// Gossipsub registration is done before we add in any new peers
   134  	// due to libp2p's gossipsub implementation not taking into
   135  	// account previously added peers when creating the gossipsub
   136  	// object.
   137  	psOpts := []pubsub.Option{
   138  		pubsub.WithMessageSignaturePolicy(pubsub.StrictNoSign),
   139  		pubsub.WithNoAuthor(),
   140  		pubsub.WithMessageIdFn(msgIDFunction),
   141  		pubsub.WithSubscriptionFilter(s),
   142  		pubsub.WithPeerOutboundQueueSize(256),
   143  		pubsub.WithValidateQueueSize(256),
   144  		pubsub.WithPeerScore(peerScoringParams()),
   145  		pubsub.WithPeerScoreInspect(s.peerInspector, time.Minute),
   146  	}
   147  	// Set the pubsub global parameters that we require.
   148  	setPubSubParameters()
   149  
   150  	gs, err := pubsub.NewGossipSub(s.ctx, s.host, psOpts...)
   151  	if err != nil {
   152  		log.WithError(err).Error("Failed to start pubsub")
   153  		return nil, err
   154  	}
   155  	s.pubsub = gs
   156  
   157  	s.peers = peers.NewStatus(ctx, &peers.StatusConfig{
   158  		PeerLimit: int(s.cfg.MaxPeers),
   159  		ScorerParams: &scorers.Config{
   160  			BadResponsesScorerConfig: &scorers.BadResponsesScorerConfig{
   161  				Threshold:     maxBadResponses,
   162  				DecayInterval: time.Hour,
   163  			},
   164  		},
   165  	})
   166  
   167  	return s, nil
   168  }
   169  
   170  // Start the p2p service.
   171  func (s *Service) Start() {
   172  	if s.started {
   173  		log.Error("Attempted to start p2p service when it was already started")
   174  		return
   175  	}
   176  
   177  	// Waits until the state is initialized via an event feed.
   178  	// Used for fork-related data when connecting peers.
   179  	s.awaitStateInitialized()
   180  	s.isPreGenesis = false
   181  
   182  	var peersToWatch []string
   183  	if s.cfg.RelayNodeAddr != "" {
   184  		peersToWatch = append(peersToWatch, s.cfg.RelayNodeAddr)
   185  		if err := dialRelayNode(s.ctx, s.host, s.cfg.RelayNodeAddr); err != nil {
   186  			log.WithError(err).Errorf("Could not dial relay node")
   187  		}
   188  	}
   189  
   190  	if !s.cfg.NoDiscovery && !s.cfg.DisableDiscv5 {
   191  		ipAddr := ipAddr()
   192  		listener, err := s.startDiscoveryV5(
   193  			ipAddr,
   194  			s.privKey,
   195  		)
   196  		if err != nil {
   197  			log.WithError(err).Fatal("Failed to start discovery")
   198  			s.startupErr = err
   199  			return
   200  		}
   201  		err = s.connectToBootnodes()
   202  		if err != nil {
   203  			log.WithError(err).Error("Could not add bootnode to the exclusion list")
   204  			s.startupErr = err
   205  			return
   206  		}
   207  		s.dv5Listener = listener
   208  		go s.listenForNewNodes()
   209  	}
   210  
   211  	s.started = true
   212  
   213  	if len(s.cfg.StaticPeers) > 0 {
   214  		addrs, err := peersFromStringAddrs(s.cfg.StaticPeers)
   215  		if err != nil {
   216  			log.Errorf("Could not connect to static peer: %v", err)
   217  		}
   218  		s.connectWithAllPeers(addrs)
   219  	}
   220  
   221  	// Periodic functions.
   222  	runutil.RunEvery(s.ctx, params.BeaconNetworkConfig().TtfbTimeout, func() {
   223  		ensurePeerConnections(s.ctx, s.host, peersToWatch...)
   224  	})
   225  	runutil.RunEvery(s.ctx, 30*time.Minute, s.Peers().Prune)
   226  	runutil.RunEvery(s.ctx, params.BeaconNetworkConfig().RespTimeout, s.updateMetrics)
   227  	runutil.RunEvery(s.ctx, refreshRate, func() {
   228  		s.RefreshENR()
   229  	})
   230  	runutil.RunEvery(s.ctx, 1*time.Minute, func() {
   231  		log.WithFields(logrus.Fields{
   232  			"inbound":     len(s.peers.InboundConnected()),
   233  			"outbound":    len(s.peers.OutboundConnected()),
   234  			"activePeers": len(s.peers.Active()),
   235  		}).Info("Peer summary")
   236  	})
   237  
   238  	multiAddrs := s.host.Network().ListenAddresses()
   239  	logIPAddr(s.host.ID(), multiAddrs...)
   240  
   241  	p2pHostAddress := s.cfg.HostAddress
   242  	p2pTCPPort := s.cfg.TCPPort
   243  
   244  	if p2pHostAddress != "" {
   245  		logExternalIPAddr(s.host.ID(), p2pHostAddress, p2pTCPPort)
   246  		verifyConnectivity(p2pHostAddress, p2pTCPPort, "tcp")
   247  	}
   248  
   249  	p2pHostDNS := s.cfg.HostDNS
   250  	if p2pHostDNS != "" {
   251  		logExternalDNSAddr(s.host.ID(), p2pHostDNS, p2pTCPPort)
   252  	}
   253  }
   254  
   255  // Stop the p2p service and terminate all peer connections.
   256  func (s *Service) Stop() error {
   257  	defer s.cancel()
   258  	s.started = false
   259  	if s.dv5Listener != nil {
   260  		s.dv5Listener.Close()
   261  	}
   262  	return nil
   263  }
   264  
   265  // Status of the p2p service. Will return an error if the service is considered unhealthy to
   266  // indicate that this node should not serve traffic until the issue has been resolved.
   267  func (s *Service) Status() error {
   268  	if s.isPreGenesis {
   269  		return nil
   270  	}
   271  	if !s.started {
   272  		return errors.New("not running")
   273  	}
   274  	if s.startupErr != nil {
   275  		return s.startupErr
   276  	}
   277  	return nil
   278  }
   279  
   280  // Started returns true if the p2p service has successfully started.
   281  func (s *Service) Started() bool {
   282  	return s.started
   283  }
   284  
   285  // Encoding returns the configured networking encoding.
   286  func (s *Service) Encoding() encoder.NetworkEncoding {
   287  	return &encoder.SszNetworkEncoder{}
   288  }
   289  
   290  // PubSub returns the p2p pubsub framework.
   291  func (s *Service) PubSub() *pubsub.PubSub {
   292  	return s.pubsub
   293  }
   294  
   295  // Host returns the currently running libp2p
   296  // host of the service.
   297  func (s *Service) Host() host.Host {
   298  	return s.host
   299  }
   300  
   301  // SetStreamHandler sets the protocol handler on the p2p host multiplexer.
   302  // This method is a pass through to libp2pcore.Host.SetStreamHandler.
   303  func (s *Service) SetStreamHandler(topic string, handler network.StreamHandler) {
   304  	s.host.SetStreamHandler(protocol.ID(topic), handler)
   305  }
   306  
   307  // PeerID returns the Peer ID of the local peer.
   308  func (s *Service) PeerID() peer.ID {
   309  	return s.host.ID()
   310  }
   311  
   312  // Disconnect from a peer.
   313  func (s *Service) Disconnect(pid peer.ID) error {
   314  	return s.host.Network().ClosePeer(pid)
   315  }
   316  
   317  // Connect to a specific peer.
   318  func (s *Service) Connect(pi peer.AddrInfo) error {
   319  	return s.host.Connect(s.ctx, pi)
   320  }
   321  
   322  // Peers returns the peer status interface.
   323  func (s *Service) Peers() *peers.Status {
   324  	return s.peers
   325  }
   326  
   327  // ENR returns the local node's current ENR.
   328  func (s *Service) ENR() *enr.Record {
   329  	if s.dv5Listener == nil {
   330  		return nil
   331  	}
   332  	return s.dv5Listener.Self().Record()
   333  }
   334  
   335  // DiscoveryAddresses represents our enr addresses as multiaddresses.
   336  func (s *Service) DiscoveryAddresses() ([]multiaddr.Multiaddr, error) {
   337  	if s.dv5Listener == nil {
   338  		return nil, nil
   339  	}
   340  	return convertToUdpMultiAddr(s.dv5Listener.Self())
   341  }
   342  
   343  // Metadata returns a copy of the peer's metadata.
   344  func (s *Service) Metadata() interfaces.Metadata {
   345  	return s.metaData.Copy()
   346  }
   347  
   348  // MetadataSeq returns the metadata sequence number.
   349  func (s *Service) MetadataSeq() uint64 {
   350  	return s.metaData.SequenceNumber()
   351  }
   352  
   353  // AddPingMethod adds the metadata ping rpc method to the p2p service, so that it can
   354  // be used to refresh ENR.
   355  func (s *Service) AddPingMethod(reqFunc func(ctx context.Context, id peer.ID) error) {
   356  	s.pingMethod = reqFunc
   357  }
   358  
   359  func (s *Service) pingPeers() {
   360  	if s.pingMethod == nil {
   361  		return
   362  	}
   363  	for _, pid := range s.peers.Connected() {
   364  		go func(id peer.ID) {
   365  			if err := s.pingMethod(s.ctx, id); err != nil {
   366  				log.WithField("peer", id).WithError(err).Debug("Failed to ping peer")
   367  			}
   368  		}(pid)
   369  	}
   370  }
   371  
   372  // Waits for the beacon state to be initialized, important
   373  // for initializing the p2p service as p2p needs to be aware
   374  // of genesis information for peering.
   375  func (s *Service) awaitStateInitialized() {
   376  	s.initializationLock.Lock()
   377  	defer s.initializationLock.Unlock()
   378  
   379  	if s.isInitialized() {
   380  		return
   381  	}
   382  
   383  	stateChannel := make(chan *feed.Event, 1)
   384  	stateSub := s.stateNotifier.StateFeed().Subscribe(stateChannel)
   385  	cleanup := stateSub.Unsubscribe
   386  	defer cleanup()
   387  	for {
   388  		select {
   389  		case event := <-stateChannel:
   390  			if event.Type == statefeed.Initialized {
   391  				data, ok := event.Data.(*statefeed.InitializedData)
   392  				if !ok {
   393  					// log.Fatalf will prevent defer from being called
   394  					cleanup()
   395  					log.Fatalf("Received wrong data over state initialized feed: %v", data)
   396  				}
   397  				s.genesisTime = data.StartTime
   398  				s.genesisValidatorsRoot = data.GenesisValidatorsRoot
   399  				_, err := s.forkDigest() // initialize fork digest cache
   400  				if err != nil {
   401  					log.WithError(err).Error("Could not initialize fork digest")
   402  				}
   403  
   404  				return
   405  			}
   406  		case <-s.ctx.Done():
   407  			log.Debug("Context closed, exiting goroutine")
   408  			return
   409  		}
   410  	}
   411  }
   412  
   413  func (s *Service) connectWithAllPeers(multiAddrs []multiaddr.Multiaddr) {
   414  	addrInfos, err := peer.AddrInfosFromP2pAddrs(multiAddrs...)
   415  	if err != nil {
   416  		log.Errorf("Could not convert to peer address info's from multiaddresses: %v", err)
   417  		return
   418  	}
   419  	for _, info := range addrInfos {
   420  		// make each dial non-blocking
   421  		go func(info peer.AddrInfo) {
   422  			if err := s.connectWithPeer(s.ctx, info); err != nil {
   423  				log.WithError(err).Tracef("Could not connect with peer %s", info.String())
   424  			}
   425  		}(info)
   426  	}
   427  }
   428  
   429  func (s *Service) connectWithPeer(ctx context.Context, info peer.AddrInfo) error {
   430  	ctx, span := trace.StartSpan(ctx, "p2p.connectWithPeer")
   431  	defer span.End()
   432  
   433  	if info.ID == s.host.ID() {
   434  		return nil
   435  	}
   436  	if s.Peers().IsBad(info.ID) {
   437  		return errors.New("refused to connect to bad peer")
   438  	}
   439  	ctx, cancel := context.WithTimeout(ctx, maxDialTimeout)
   440  	defer cancel()
   441  	if err := s.host.Connect(ctx, info); err != nil {
   442  		s.Peers().Scorers().BadResponsesScorer().Increment(info.ID)
   443  		return err
   444  	}
   445  	return nil
   446  }
   447  
   448  func (s *Service) connectToBootnodes() error {
   449  	nodes := make([]*enode.Node, 0, len(s.cfg.Discv5BootStrapAddr))
   450  	for _, addr := range s.cfg.Discv5BootStrapAddr {
   451  		bootNode, err := enode.Parse(enode.ValidSchemes, addr)
   452  		if err != nil {
   453  			return err
   454  		}
   455  		// do not dial bootnodes with their tcp ports not set
   456  		if err := bootNode.Record().Load(enr.WithEntry("tcp", new(enr.TCP))); err != nil {
   457  			if !enr.IsNotFound(err) {
   458  				log.WithError(err).Error("Could not retrieve tcp port")
   459  			}
   460  			continue
   461  		}
   462  		nodes = append(nodes, bootNode)
   463  	}
   464  	multiAddresses := convertToMultiAddr(nodes)
   465  	s.connectWithAllPeers(multiAddresses)
   466  	return nil
   467  }
   468  
   469  // Returns true if the service is aware of the genesis time and genesis validator root. This is
   470  // required for discovery and pubsub validation.
   471  func (s *Service) isInitialized() bool {
   472  	return !s.genesisTime.IsZero() && len(s.genesisValidatorsRoot) == 32
   473  }