github.com/amazechain/amc@v0.1.3/internal/p2p/service.go (about) 1 // Package p2p defines the network protocol implementation for amc consensus 2 // used by amc, 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 "fmt" 10 "github.com/amazechain/amc/api/protocol/sync_pb" 11 "github.com/amazechain/amc/common" 12 "github.com/amazechain/amc/common/types" 13 "github.com/amazechain/amc/conf" 14 "github.com/amazechain/amc/internal/p2p/encoder" 15 "github.com/amazechain/amc/internal/p2p/enode" 16 "github.com/amazechain/amc/internal/p2p/enr" 17 leakybucket "github.com/amazechain/amc/internal/p2p/leaky-bucket" 18 "github.com/amazechain/amc/internal/p2p/peers" 19 "github.com/amazechain/amc/internal/p2p/peers/scorers" 20 "github.com/amazechain/amc/utils" 21 "google.golang.org/protobuf/proto" 22 "sync" 23 "time" 24 25 "github.com/libp2p/go-libp2p" 26 pubsub "github.com/libp2p/go-libp2p-pubsub" 27 "github.com/libp2p/go-libp2p/core/host" 28 "github.com/libp2p/go-libp2p/core/network" 29 "github.com/libp2p/go-libp2p/core/peer" 30 "github.com/libp2p/go-libp2p/core/protocol" 31 "github.com/multiformats/go-multiaddr" 32 "github.com/pkg/errors" 33 "go.opencensus.io/trace" 34 ) 35 36 //todo 37 //var _ runtime.Service = (*Service)(nil) 38 39 // In the event that we are at our peer limit, we 40 // stop looking for new peers and instead poll 41 // for the current peer limit status for the time period 42 // defined below. 43 var pollingPeriod = 6 * time.Second 44 45 // Refresh rate of ENR set at twice per block. 46 var refreshRate = 16 * time.Second 47 48 // maxBadResponses is the maximum number of bad responses from a peer before we stop talking to it. 49 const maxBadResponses = 5 50 51 // pubsubQueueSize is the size that we assign to our validation queue and outbound message queue for 52 // gossipsub. 53 const pubsubQueueSize = 600 54 55 // maxDialTimeout is the timeout for a single peer dial. 56 const maxDialTimeout = 10 * time.Second 57 58 // todo 59 const ttfbTimeout = 10 * time.Second // TtfbTimeout is the maximum time to wait for first byte of request response (time-to-first-byte). 60 61 // todo 62 const reconnectBootNode = 1 * time.Minute 63 64 // Service for managing peer to peer (p2p) networking. 65 type Service struct { 66 started bool 67 isPreGenesis bool 68 pingMethod func(ctx context.Context, id peer.ID) error 69 cancel context.CancelFunc 70 cfg *conf.P2PConfig 71 peers *peers.Status 72 addrFilter *multiaddr.Filters 73 ipLimiter *leakybucket.Collector 74 privKey *ecdsa.PrivateKey 75 pubsub *pubsub.PubSub 76 joinedTopics map[string]*pubsub.Topic 77 joinedTopicsLock sync.Mutex 78 dv5Listener Listener 79 startupErr error 80 ctx context.Context 81 host host.Host 82 genesisHash types.Hash 83 genesisValidatorsRoot []byte 84 activeValidatorCount uint64 85 ping *sync_pb.Ping 86 wg sync.WaitGroup 87 } 88 89 // NewService initializes a new p2p service compatible with shared.Service interface. No 90 // connections are made until the Start function is called during the service registry startup. 91 func NewService(ctx context.Context, genesisHash types.Hash, cfg *conf.P2PConfig, nodeCfg conf.NodeConfig) (*Service, error) { 92 var err error 93 ctx, cancel := context.WithCancel(ctx) 94 _ = cancel // govet fix for lost cancel. Cancel is handled in service.Stop(). 95 96 s := &Service{ 97 ctx: ctx, 98 cancel: cancel, 99 cfg: cfg, 100 isPreGenesis: true, 101 joinedTopics: make(map[string]*pubsub.Topic, len(gossipTopicMappings)), 102 genesisHash: genesisHash, 103 } 104 105 s.ping, err = getSeqNumber(s.cfg) 106 if err != nil { 107 log.Error("Failed to generate p2p seq number", "err", err) 108 return nil, err 109 } 110 111 dv5Nodes := parseBootStrapAddrs(cfg.BootstrapNodeAddr, nodeCfg) 112 // 113 cfg.Discv5BootStrapAddr = dv5Nodes 114 115 ipAddr := utils.IPAddr() 116 s.privKey, err = privKey(s.cfg) 117 if err != nil { 118 log.Error("Failed to generate p2p private key", "err", err) 119 return nil, err 120 } 121 s.addrFilter, err = configureFilter(s.cfg) 122 if err != nil { 123 log.Error("Failed to create address filter", "err", err) 124 return nil, err 125 } 126 //todo 127 s.ipLimiter = leakybucket.NewCollector(ipLimit, ipBurst, 30*time.Second, true /* deleteEmptyBuckets */) 128 129 opts := s.buildOptions(ipAddr, s.privKey) 130 h, err := libp2p.New(opts...) 131 if err != nil { 132 log.Error("Failed to create p2p host", "err", err) 133 return nil, err 134 } 135 136 s.host = h 137 // Gossipsub registration is done before we add in any new peers 138 // due to libp2p's gossipsub implementation not taking into 139 // account previously added peers when creating the gossipsub 140 // object. 141 psOpts := s.pubsubOptions() 142 // Set the pubsub global parameters that we require. 143 setPubSubParameters() 144 145 gs, err := pubsub.NewGossipSub(s.ctx, s.host, psOpts...) 146 if err != nil { 147 log.Error("Failed to start pubsub", "err", err) 148 return nil, err 149 } 150 s.pubsub = gs 151 152 s.peers = peers.NewStatus(ctx, &peers.StatusConfig{ 153 PeerLimit: s.cfg.MaxPeers, 154 ScorerParams: &scorers.Config{ 155 BadResponsesScorerConfig: &scorers.BadResponsesScorerConfig{ 156 Threshold: maxBadResponses, 157 DecayInterval: 10 * time.Minute, 158 }, 159 }, 160 }) 161 162 // Initialize Data maps. 163 //types.InitializeDataMaps() 164 165 return s, nil 166 } 167 168 func (s *Service) GetPing() *sync_pb.Ping { 169 return proto.Clone(s.ping).(*sync_pb.Ping) 170 } 171 172 func (s *Service) IncSeqNumber() { 173 s.ping.SeqNumber++ 174 } 175 176 func (s *Service) GetConfig() *conf.P2PConfig { 177 return s.cfg 178 } 179 180 // Start the p2p service. 181 func (s *Service) Start() { 182 if s.started { 183 log.Error("Attempted to start p2p service when it was already started") 184 return 185 } 186 187 s.isPreGenesis = false 188 189 var peersToWatch []string 190 if s.cfg.RelayNodeAddr != "" { 191 peersToWatch = append(peersToWatch, s.cfg.RelayNodeAddr) 192 if err := dialRelayNode(s.ctx, s.host, s.cfg.RelayNodeAddr); err != nil { 193 log.Error("Could not dial relay node", "err", err) 194 } 195 } 196 197 if !s.cfg.NoDiscovery { 198 ipAddr := utils.IPAddr() 199 listener, err := s.startDiscoveryV5( 200 ipAddr, 201 s.privKey, 202 ) 203 if err != nil { 204 log.Crit("Failed to start discovery", "err", err) 205 s.startupErr = err 206 return 207 } 208 bootnodes, err := s.bootnodes() 209 if err != nil { 210 s.startupErr = err 211 return 212 } 213 err = s.connectToBootnodes(bootnodes) 214 if err != nil { 215 log.Error("Could not add bootnode to the exclusion list", "err", err) 216 s.startupErr = err 217 return 218 } 219 s.dv5Listener = listener 220 go s.listenForNewNodes() 221 utils.RunEvery(s.ctx, reconnectBootNode, func() { 222 s.ensureBootPeerConnections(bootnodes) 223 }) 224 225 } 226 227 s.started = true 228 229 if len(s.cfg.StaticPeers) > 0 { 230 addrs, err := PeersFromStringAddrs(s.cfg.StaticPeers) 231 if err != nil { 232 log.Error("Could not connect to static peer", "err", err) 233 } 234 s.connectWithAllPeers(addrs) 235 } 236 // Initialize metadata according to the 237 // current epoch. 238 s.RefreshENR() 239 240 // Periodic functions. 241 if len(peersToWatch) > 0 { 242 utils.RunEvery(s.ctx, ttfbTimeout, func() { 243 ensurePeerConnections(s.ctx, s.host, peersToWatch...) 244 }) 245 } 246 247 utils.RunEvery(s.ctx, 30*time.Minute, s.Peers().Prune) 248 utils.RunEvery(s.ctx, 10*time.Second, s.updateMetrics) 249 utils.RunEvery(s.ctx, refreshRate, s.RefreshENR) 250 utils.RunEvery(s.ctx, 1*time.Minute, func() { 251 //utils.RunEvery(s.ctx, 5*time.Second, func() { 252 log.Info("Peer summary", "inbound", len(s.peers.InboundConnected()), "outbound", len(s.peers.OutboundConnected()), "activePeers", len(s.peers.Active()), "disconnectedPeers", len(s.peers.Disconnected())) 253 for _, p := range s.peers.All() { 254 //addr, _ := s.peers.Address(p) 255 //IP, _ := s.peers.IP(p) 256 //ENR, _ := s.peers.ENR(p) 257 258 params := make([]interface{}, 0) 259 params = append(params, "perrId", p) 260 261 if dialArgs, err := s.peers.DialArgs(p); err == nil { 262 params = append(params, "dialArgs", dialArgs) 263 } 264 if direction, err := s.peers.Direction(p); err == nil { 265 params = append(params, "Direction", direction) 266 } 267 if connState, err := s.peers.ConnState(p); err == nil { 268 params = append(params, "connState", connState) 269 } 270 if chainState, err := s.peers.ChainState(p); err == nil { 271 params = append(params, "currentHeight", utils.ConvertH256ToUint256Int(chainState.CurrentHeight).Uint64()) 272 } 273 if nextValidTime, err := s.peers.NextValidTime(p); err == nil && time.Now().After(nextValidTime) == false { 274 params = append(params, "nextValidTime", common.PrettyDuration(time.Until(nextValidTime))) 275 } 276 if badResponses, err := s.peers.Scorers().BadResponsesScorer().Count(p); err == nil { 277 params = append(params, "badResponses", badResponses) 278 } 279 if validationError := s.peers.Scorers().ValidationError(p); validationError != nil { 280 params = append(params, "validationError", validationError) 281 } 282 if ping, err := s.peers.GetPing(p); err == nil && ping != nil { 283 params = append(params, "ping", ping.String()) 284 } 285 286 params = append(params, "processedBlocks", s.peers.Scorers().BlockProviderScorer().ProcessedBlocks(p)) 287 288 // hexutil.Encode([]byte(p)) 289 log.Info("Peer details", params...) 290 291 log.Info("Peer Score:", 292 "badResponsesScore", s.peers.Scorers().BadResponsesScorer().Score(p), 293 "blockProviderScore", s.peers.Scorers().BlockProviderScorer().Score(p), 294 "peerStatusScore", s.peers.Scorers().PeerStatusScorer().Score(p), 295 "gossipScore", s.peers.Scorers().GossipScorer().Score(p), 296 "Score", s.peers.Scorers().Score(p), 297 ) 298 pids, _ := s.host.Peerstore().SupportsProtocols(p, s.host.Mux().Protocols()...) 299 for _, id := range pids { 300 log.Trace("Protocol details:", "ProtocolID", id) 301 } 302 } 303 304 allNodes := s.dv5Listener.AllNodes() 305 log.Trace("Nodes stored in the discovery table:") 306 for i, n := range allNodes { 307 log.Trace(fmt.Sprintf("P2P details %d", i), "ENR", n.String(), "Node ID", n.ID(), "IP", n.IP(), "UDP", n.UDP(), "TCP", n.TCP()) 308 } 309 }) 310 311 multiAddrs := s.host.Network().ListenAddresses() 312 logIPAddr(s.host.ID(), multiAddrs...) 313 314 p2pHostAddress := s.cfg.HostAddress 315 p2pTCPPort := s.cfg.TCPPort 316 317 if p2pHostAddress != "" { 318 logExternalIPAddr(s.host.ID(), p2pHostAddress, p2pTCPPort) 319 verifyConnectivity(p2pHostAddress, p2pTCPPort, "tcp") 320 } 321 322 p2pHostDNS := s.cfg.HostDNS 323 if p2pHostDNS != "" { 324 logExternalDNSAddr(s.host.ID(), p2pHostDNS, p2pTCPPort) 325 } 326 //todo 327 //go s.forkWatcher() 328 go s.loop() 329 s.wg.Add(1) 330 } 331 332 func (s *Service) loop() { 333 defer log.Debug("Context closed, exiting goroutine") 334 for { 335 select { 336 case <-s.ctx.Done(): 337 log.Info("start write seq number to file") 338 err := saveSeqNumber(s.cfg, s.GetPing()) 339 if err != nil { 340 //log.Error("") 341 } 342 s.wg.Done() 343 return 344 345 } 346 } 347 } 348 349 // Stop the p2p service and terminate all peer connections. 350 func (s *Service) Stop() error { 351 s.cancel() 352 s.started = false 353 if s.dv5Listener != nil { 354 s.dv5Listener.Close() 355 } 356 s.wg.Wait() 357 log.Info("P2P service stopped") 358 return nil 359 } 360 361 // Status of the p2p service. Will return an error if the service is considered unhealthy to 362 // indicate that this node should not serve traffic until the issue has been resolved. 363 func (s *Service) Status() error { 364 if s.isPreGenesis { 365 return nil 366 } 367 if !s.started { 368 return errors.New("not running") 369 } 370 if s.startupErr != nil { 371 return s.startupErr 372 } 373 return nil 374 } 375 376 // Started returns true if the p2p service has successfully started. 377 func (s *Service) Started() bool { 378 return s.started 379 } 380 381 // Encoding returns the configured networking encoding. 382 func (_ *Service) Encoding() encoder.NetworkEncoding { 383 return &encoder.SszNetworkEncoder{} 384 } 385 386 // PubSub returns the p2p pubsub framework. 387 func (s *Service) PubSub() *pubsub.PubSub { 388 return s.pubsub 389 } 390 391 // Host returns the currently running libp2p 392 // host of the service. 393 func (s *Service) Host() host.Host { 394 return s.host 395 } 396 397 // SetStreamHandler sets the protocol handler on the p2p host multiplexer. 398 // This method is a pass through to libp2pcore.Host.SetStreamHandler. 399 func (s *Service) SetStreamHandler(topic string, handler network.StreamHandler) { 400 s.host.SetStreamHandler(protocol.ID(topic), handler) 401 } 402 403 // PeerID returns the Peer ID of the local peer. 404 func (s *Service) PeerID() peer.ID { 405 return s.host.ID() 406 } 407 408 // Disconnect from a peer. 409 func (s *Service) Disconnect(pid peer.ID) error { 410 return s.host.Network().ClosePeer(pid) 411 } 412 413 // Connect to a specific peer. 414 func (s *Service) Connect(pi peer.AddrInfo) error { 415 return s.host.Connect(s.ctx, pi) 416 } 417 418 // Peers returns the peer status interface. 419 func (s *Service) Peers() *peers.Status { 420 return s.peers 421 } 422 423 // ENR returns the local node's current ENR. 424 func (s *Service) ENR() *enr.Record { 425 if s.dv5Listener == nil { 426 return nil 427 } 428 return s.dv5Listener.Self().Record() 429 } 430 431 // DiscoveryAddresses represents our enr addresses as multiaddresses. 432 func (s *Service) DiscoveryAddresses() ([]multiaddr.Multiaddr, error) { 433 if s.dv5Listener == nil { 434 return nil, nil 435 } 436 return convertToUdpMultiAddr(s.dv5Listener.Self()) 437 } 438 439 // AddPingMethod adds the metadata ping rpc method to the p2p service, so that it can 440 // be used to refresh ENR. 441 func (s *Service) AddPingMethod(reqFunc func(ctx context.Context, id peer.ID) error) { 442 s.pingMethod = reqFunc 443 } 444 445 func (s *Service) pingPeers() { 446 if s.pingMethod == nil { 447 return 448 } 449 for _, pid := range s.peers.Connected() { 450 go func(id peer.ID) { 451 if err := s.pingMethod(s.ctx, id); err != nil { 452 log.Debug("Failed to ping peer", "peer", id, "err", err) 453 } 454 }(pid) 455 } 456 } 457 458 func (s *Service) connectWithAllPeers(multiAddrs []multiaddr.Multiaddr) { 459 addrInfos, err := peer.AddrInfosFromP2pAddrs(multiAddrs...) 460 if err != nil { 461 log.Error("Could not convert to peer address info's from multiaddresses", "err", err) 462 return 463 } 464 for _, info := range addrInfos { 465 // make each dial non-blocking 466 go func(info peer.AddrInfo) { 467 if err := s.connectWithPeer(s.ctx, info); err != nil { 468 log.Trace(fmt.Sprintf("Could not connect with peer %s", info.String()), "err", err) 469 } 470 }(info) 471 } 472 } 473 474 func (s *Service) connectWithPeer(ctx context.Context, info peer.AddrInfo) error { 475 ctx, span := trace.StartSpan(ctx, "p2p.connectWithPeer") 476 defer span.End() 477 478 if info.ID == s.host.ID() { 479 log.Warn("bootNode ID == localNode ID") 480 return nil 481 } 482 if s.Peers().IsBad(info.ID) { 483 return errors.New("refused to connect to bad peer") 484 } 485 ctx, cancel := context.WithTimeout(ctx, maxDialTimeout) 486 defer cancel() 487 488 log.Debug("start connect", "peer info", info) 489 if err := s.host.Connect(ctx, info); err != nil { 490 s.Peers().Scorers().BadResponsesScorer().Increment(info.ID) 491 return err 492 } 493 return nil 494 } 495 496 func (s *Service) bootnodes() ([]multiaddr.Multiaddr, error) { 497 nodes := make([]*enode.Node, 0, len(s.cfg.Discv5BootStrapAddr)) 498 for _, addr := range s.cfg.Discv5BootStrapAddr { 499 bootNode, err := enode.Parse(enode.ValidSchemes, addr) 500 if err != nil { 501 return []multiaddr.Multiaddr{}, err 502 } 503 // do not dial bootnodes with their tcp ports not set 504 if err := bootNode.Record().Load(enr.WithEntry("tcp", new(enr.TCP))); err != nil { 505 if !enr.IsNotFound(err) { 506 log.Error("Could not retrieve tcp port", "err", err) 507 } 508 continue 509 } 510 nodes = append(nodes, bootNode) 511 } 512 return convertToMultiAddr(nodes), nil 513 } 514 515 func (s *Service) connectToBootnodes(nodes []multiaddr.Multiaddr) error { 516 s.connectWithAllPeers(nodes) 517 return nil 518 } 519 520 // ensureBootPeerConnections will attempt to reestablish connection to the peers 521 // if there are currently no connections to that peer. 522 func (s *Service) ensureBootPeerConnections(bootnodes []multiaddr.Multiaddr) { 523 524 addrInfos, err := peer.AddrInfosFromP2pAddrs(bootnodes...) 525 if err != nil { 526 log.Error("Could not convert to peer address info's from multiaddresses", "err", err) 527 return 528 } 529 for _, info := range addrInfos { 530 // make each dial non-blocking 531 if connState, err := s.peers.ConnState(info.ID); err != nil || connState != peers.PeerDisconnected { 532 continue 533 } 534 if nextValidTime, err := s.peers.NextValidTime(info.ID); err != nil || !time.Now().After(nextValidTime) { 535 continue 536 } 537 go func(info peer.AddrInfo) { 538 if err := s.connectWithPeer(s.ctx, info); err != nil { 539 log.Warn(fmt.Sprintf("Could not connect with bootnode %s", info.String()), "err", err) 540 } 541 }(info) 542 } 543 }