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 }