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  }