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 }