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

     1  package p2p
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"sync"
     9  	"time"
    10  
    11  	"github.com/libp2p/go-libp2p-core/network"
    12  	"github.com/libp2p/go-libp2p-core/peer"
    13  	"github.com/prysmaticlabs/prysm/beacon-chain/p2p/peers"
    14  	"github.com/prysmaticlabs/prysm/beacon-chain/p2p/peers/peerdata"
    15  	"github.com/prysmaticlabs/prysm/shared/timeutils"
    16  	"github.com/sirupsen/logrus"
    17  )
    18  
    19  const (
    20  	// The time to wait for a status request.
    21  	timeForStatus = 10 * time.Second
    22  )
    23  
    24  func peerMultiaddrString(conn network.Conn) string {
    25  	return fmt.Sprintf("%s/p2p/%s", conn.RemoteMultiaddr().String(), conn.RemotePeer().String())
    26  }
    27  
    28  // AddConnectionHandler adds a callback function which handles the connection with a
    29  // newly added peer. It performs a handshake with that peer by sending a hello request
    30  // and validating the response from the peer.
    31  func (s *Service) AddConnectionHandler(reqFunc, goodByeFunc func(ctx context.Context, id peer.ID) error) {
    32  	// Peer map and lock to keep track of current connection attempts.
    33  	peerMap := make(map[peer.ID]bool)
    34  	peerLock := new(sync.Mutex)
    35  
    36  	// This is run at the start of each connection attempt, to ensure
    37  	// that there aren't multiple inflight connection requests for the
    38  	// same peer at once.
    39  	peerHandshaking := func(id peer.ID) bool {
    40  		peerLock.Lock()
    41  		defer peerLock.Unlock()
    42  
    43  		if peerMap[id] {
    44  			repeatPeerConnections.Inc()
    45  			return true
    46  		}
    47  
    48  		peerMap[id] = true
    49  		return false
    50  	}
    51  
    52  	peerFinished := func(id peer.ID) {
    53  		peerLock.Lock()
    54  		defer peerLock.Unlock()
    55  
    56  		delete(peerMap, id)
    57  	}
    58  
    59  	s.host.Network().Notify(&network.NotifyBundle{
    60  		ConnectedF: func(net network.Network, conn network.Conn) {
    61  			remotePeer := conn.RemotePeer()
    62  			disconnectFromPeer := func() {
    63  				s.peers.SetConnectionState(remotePeer, peers.PeerDisconnecting)
    64  				// Only attempt a goodbye if we are still connected to the peer.
    65  				if s.host.Network().Connectedness(remotePeer) == network.Connected {
    66  					if err := goodByeFunc(context.TODO(), remotePeer); err != nil {
    67  						log.WithError(err).Error("Unable to disconnect from peer")
    68  					}
    69  				}
    70  				s.peers.SetConnectionState(remotePeer, peers.PeerDisconnected)
    71  			}
    72  			// Connection handler must be non-blocking as part of libp2p design.
    73  			go func() {
    74  				if peerHandshaking(remotePeer) {
    75  					// Exit this if there is already another connection
    76  					// request in flight.
    77  					return
    78  				}
    79  				defer peerFinished(remotePeer)
    80  				// Handle the various pre-existing conditions that will result in us not handshaking.
    81  				peerConnectionState, err := s.peers.ConnectionState(remotePeer)
    82  				if err == nil && (peerConnectionState == peers.PeerConnected || peerConnectionState == peers.PeerConnecting) {
    83  					log.WithField("currentState", peerConnectionState).WithField("reason", "already active").Trace("Ignoring connection request")
    84  					return
    85  				}
    86  				s.peers.Add(nil /* ENR */, remotePeer, conn.RemoteMultiaddr(), conn.Stat().Direction)
    87  				// Defensive check in the event we still get a bad peer.
    88  				if s.peers.IsBad(remotePeer) {
    89  					log.WithField("reason", "bad peer").Trace("Ignoring connection request")
    90  					disconnectFromPeer()
    91  					return
    92  				}
    93  				validPeerConnection := func() {
    94  					s.peers.SetConnectionState(conn.RemotePeer(), peers.PeerConnected)
    95  					// Go through the handshake process.
    96  					log.WithFields(logrus.Fields{
    97  						"direction":   conn.Stat().Direction,
    98  						"multiAddr":   peerMultiaddrString(conn),
    99  						"activePeers": len(s.peers.Active()),
   100  					}).Debug("Peer connected")
   101  				}
   102  
   103  				// Do not perform handshake on inbound dials.
   104  				if conn.Stat().Direction == network.DirInbound {
   105  					_, err := s.peers.ChainState(remotePeer)
   106  					peerExists := err == nil
   107  					currentTime := timeutils.Now()
   108  
   109  					// Wait for peer to initiate handshake
   110  					time.Sleep(timeForStatus)
   111  
   112  					// Exit if we are disconnected with the peer.
   113  					if s.host.Network().Connectedness(remotePeer) != network.Connected {
   114  						return
   115  					}
   116  
   117  					// If peer hasn't sent a status request, we disconnect with them
   118  					if _, err := s.peers.ChainState(remotePeer); errors.Is(err, peerdata.ErrPeerUnknown) {
   119  						disconnectFromPeer()
   120  						return
   121  					}
   122  					if peerExists {
   123  						updated, err := s.peers.ChainStateLastUpdated(remotePeer)
   124  						if err != nil {
   125  							disconnectFromPeer()
   126  							return
   127  						}
   128  						// exit if we don't receive any current status messages from
   129  						// peer.
   130  						if updated.IsZero() || !updated.After(currentTime) {
   131  							disconnectFromPeer()
   132  							return
   133  						}
   134  					}
   135  					validPeerConnection()
   136  					return
   137  				}
   138  
   139  				s.peers.SetConnectionState(conn.RemotePeer(), peers.PeerConnecting)
   140  				if err := reqFunc(context.TODO(), conn.RemotePeer()); err != nil && err != io.EOF {
   141  					log.WithError(err).Trace("Handshake failed")
   142  					disconnectFromPeer()
   143  					return
   144  				}
   145  				validPeerConnection()
   146  			}()
   147  		},
   148  	})
   149  }
   150  
   151  // AddDisconnectionHandler disconnects from peers.  It handles updating the peer status.
   152  // This also calls the handler responsible for maintaining other parts of the sync or p2p system.
   153  func (s *Service) AddDisconnectionHandler(handler func(ctx context.Context, id peer.ID) error) {
   154  	s.host.Network().Notify(&network.NotifyBundle{
   155  		DisconnectedF: func(net network.Network, conn network.Conn) {
   156  			log := log.WithField("multiAddr", peerMultiaddrString(conn))
   157  			// Must be handled in a goroutine as this callback cannot be blocking.
   158  			go func() {
   159  				// Exit early if we are still connected to the peer.
   160  				if net.Connectedness(conn.RemotePeer()) == network.Connected {
   161  					return
   162  				}
   163  				priorState, err := s.peers.ConnectionState(conn.RemotePeer())
   164  				if err != nil {
   165  					// Can happen if the peer has already disconnected, so...
   166  					priorState = peers.PeerDisconnected
   167  				}
   168  				s.peers.SetConnectionState(conn.RemotePeer(), peers.PeerDisconnecting)
   169  				if err := handler(context.TODO(), conn.RemotePeer()); err != nil {
   170  					log.WithError(err).Error("Disconnect handler failed")
   171  				}
   172  				s.peers.SetConnectionState(conn.RemotePeer(), peers.PeerDisconnected)
   173  				// Only log disconnections if we were fully connected.
   174  				if priorState == peers.PeerConnected {
   175  					log.WithField("activePeers", len(s.peers.Active())).Debug("Peer disconnected")
   176  				}
   177  			}()
   178  		},
   179  	})
   180  }