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 }