github.com/amazechain/amc@v0.1.3/internal/p2p/handshake.go (about)

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