github.com/dim4egster/coreth@v0.10.2/peer/network.go (about) 1 // (c) 2019-2022, Ava Labs, Inc. All rights reserved. 2 // See the file LICENSE for licensing terms. 3 4 package peer 5 6 import ( 7 "context" 8 "errors" 9 "fmt" 10 "sync" 11 "time" 12 13 "golang.org/x/sync/semaphore" 14 15 "github.com/ethereum/go-ethereum/log" 16 17 "github.com/dim4egster/qmallgo/codec" 18 "github.com/dim4egster/qmallgo/ids" 19 "github.com/dim4egster/qmallgo/snow/engine/common" 20 "github.com/dim4egster/qmallgo/snow/validators" 21 "github.com/dim4egster/qmallgo/version" 22 23 "github.com/dim4egster/coreth/peer/stats" 24 "github.com/dim4egster/coreth/plugin/evm/message" 25 ) 26 27 // Minimum amount of time to handle a request 28 const minRequestHandlingDuration = 100 * time.Millisecond 29 30 var ( 31 errAcquiringSemaphore = errors.New("error acquiring semaphore") 32 _ Network = &network{} 33 _ validators.Connector = &network{} 34 _ common.AppHandler = &network{} 35 ) 36 37 type Network interface { 38 validators.Connector 39 common.AppHandler 40 41 // RequestAny synchronously sends request to a randomly chosen peer with a 42 // node version greater than or equal to minVersion. 43 // Returns the ID of the chosen peer, and an error if the request could not 44 // be sent to a peer with the desired [minVersion]. 45 RequestAny(minVersion *version.Application, message []byte, handler message.ResponseHandler) (ids.NodeID, error) 46 47 // Request sends message to given nodeID, notifying handler when there's a response or timeout 48 Request(nodeID ids.NodeID, message []byte, handler message.ResponseHandler) error 49 50 // Gossip sends given gossip message to peers 51 Gossip(gossip []byte) error 52 53 // Shutdown stops all peer channel listeners and marks the node to have stopped 54 // n.Start() can be called again but the peers will have to be reconnected 55 // by calling OnPeerConnected for each peer 56 Shutdown() 57 58 // SetGossipHandler sets the provided gossip handler as the gossip handler 59 SetGossipHandler(handler message.GossipHandler) 60 61 // SetRequestHandler sets the provided request handler as the request handler 62 SetRequestHandler(handler message.RequestHandler) 63 64 // Size returns the size of the network in number of connected peers 65 Size() uint32 66 67 // TrackBandwidth should be called for each valid request with the bandwidth 68 // (length of response divided by request time), and with 0 if the response is invalid. 69 TrackBandwidth(nodeID ids.NodeID, bandwidth float64) 70 } 71 72 // network is an implementation of Network that processes message requests for 73 // each peer in linear fashion 74 type network struct { 75 lock sync.RWMutex // lock for mutating state of this Network struct 76 self ids.NodeID // NodeID of this node 77 requestIDGen uint32 // requestID counter used to track outbound requests 78 outstandingRequestHandlers map[uint32]message.ResponseHandler // maps qmallgo requestID => message.ResponseHandler 79 activeRequests *semaphore.Weighted // controls maximum number of active outbound requests 80 appSender common.AppSender // qmallgo AppSender for sending messages 81 codec codec.Manager // Codec used for parsing messages 82 requestHandler message.RequestHandler // maps request type => handler 83 gossipHandler message.GossipHandler // maps gossip type => handler 84 peers *peerTracker // tracking of peers & bandwidth 85 stats stats.RequestHandlerStats // Provide request handler metrics 86 } 87 88 func NewNetwork(appSender common.AppSender, codec codec.Manager, self ids.NodeID, maxActiveRequests int64) Network { 89 return &network{ 90 appSender: appSender, 91 codec: codec, 92 self: self, 93 outstandingRequestHandlers: make(map[uint32]message.ResponseHandler), 94 activeRequests: semaphore.NewWeighted(maxActiveRequests), 95 gossipHandler: message.NoopMempoolGossipHandler{}, 96 requestHandler: message.NoopRequestHandler{}, 97 peers: NewPeerTracker(), 98 stats: stats.NewRequestHandlerStats(), 99 } 100 } 101 102 // RequestAny synchronously sends request to a randomly chosen peer with a 103 // node version greater than or equal to minVersion. If minVersion is nil, 104 // the request will be sent to any peer regardless of their version. 105 // Returns the ID of the chosen peer, and an error if the request could not 106 // be sent to a peer with the desired [minVersion]. 107 func (n *network) RequestAny(minVersion *version.Application, request []byte, handler message.ResponseHandler) (ids.NodeID, error) { 108 // Take a slot from total [activeRequests] and block until a slot becomes available. 109 if err := n.activeRequests.Acquire(context.Background(), 1); err != nil { 110 return ids.EmptyNodeID, errAcquiringSemaphore 111 } 112 113 n.lock.Lock() 114 defer n.lock.Unlock() 115 if nodeID, ok := n.peers.GetAnyPeer(minVersion); ok { 116 return nodeID, n.request(nodeID, request, handler) 117 } 118 119 n.activeRequests.Release(1) 120 return ids.EmptyNodeID, fmt.Errorf("no peers found matching version %s out of %d peers", minVersion, n.peers.Size()) 121 } 122 123 // Request sends request message bytes to specified nodeID, notifying the responseHandler on response or failure 124 func (n *network) Request(nodeID ids.NodeID, request []byte, responseHandler message.ResponseHandler) error { 125 if nodeID == ids.EmptyNodeID { 126 return fmt.Errorf("cannot send request to empty nodeID, nodeID=%s, requestLen=%d", nodeID, len(request)) 127 } 128 129 // Take a slot from total [activeRequests] and block until a slot becomes available. 130 if err := n.activeRequests.Acquire(context.Background(), 1); err != nil { 131 return errAcquiringSemaphore 132 } 133 134 n.lock.Lock() 135 defer n.lock.Unlock() 136 137 return n.request(nodeID, request, responseHandler) 138 } 139 140 // request sends request message bytes to specified nodeID and adds [responseHandler] to [outstandingRequestHandlers] 141 // so that it can be invoked when the network receives either a response or failure message. 142 // Assumes [nodeID] is never [self] since we guarantee [self] will not be added to the [peers] map. 143 // Releases active requests semaphore if there was an error in sending the request 144 // Returns an error if [appSender] is unable to make the request. 145 // Assumes write lock is held 146 func (n *network) request(nodeID ids.NodeID, request []byte, responseHandler message.ResponseHandler) error { 147 log.Debug("sending request to peer", "nodeID", nodeID, "requestLen", len(request)) 148 n.peers.TrackPeer(nodeID) 149 150 // generate requestID 151 requestID := n.requestIDGen 152 n.requestIDGen++ 153 154 n.outstandingRequestHandlers[requestID] = responseHandler 155 156 nodeIDs := ids.NewNodeIDSet(1) 157 nodeIDs.Add(nodeID) 158 159 // send app request to the peer 160 // on failure: release the activeRequests slot, mark message as processed and return fatal error 161 // Send app request to [nodeID]. 162 // On failure, release the slot from active requests and [outstandingRequestHandlers]. 163 if err := n.appSender.SendAppRequest(nodeIDs, requestID, request); err != nil { 164 n.activeRequests.Release(1) 165 delete(n.outstandingRequestHandlers, requestID) 166 return err 167 } 168 169 log.Debug("sent request message to peer", "nodeID", nodeID, "requestID", requestID) 170 return nil 171 } 172 173 // AppRequest is called by qmallgo -> VM when there is an incoming AppRequest from a peer 174 // error returned by this function is expected to be treated as fatal by the engine 175 // returns error if the requestHandler returns an error 176 // sends a response back to the sender if length of response returned by the handler is >0 177 // expects the deadline to not have been passed 178 func (n *network) AppRequest(nodeID ids.NodeID, requestID uint32, deadline time.Time, request []byte) error { 179 n.lock.RLock() 180 defer n.lock.RUnlock() 181 182 log.Debug("received AppRequest from node", "nodeID", nodeID, "requestID", requestID, "requestLen", len(request)) 183 184 var req message.Request 185 if _, err := n.codec.Unmarshal(request, &req); err != nil { 186 log.Debug("failed to unmarshal app request", "nodeID", nodeID, "requestID", requestID, "requestLen", len(request), "err", err) 187 return nil 188 } 189 190 // calculate how much time is left until the deadline 191 timeTillDeadline := time.Until(deadline) 192 n.stats.UpdateTimeUntilDeadline(timeTillDeadline) 193 194 // bufferedDeadline is half the time till actual deadline so that the message has a reasonable chance 195 // of completing its processing and sending the response to the peer. 196 timeTillDeadline = time.Duration(timeTillDeadline.Nanoseconds() / 2) 197 bufferedDeadline := time.Now().Add(timeTillDeadline) 198 199 // check if we have enough time to handle this request 200 if time.Until(bufferedDeadline) < minRequestHandlingDuration { 201 // Drop the request if we already missed the deadline to respond. 202 log.Debug("deadline to process AppRequest has expired, skipping", "nodeID", nodeID, "requestID", requestID, "req", req) 203 n.stats.IncDeadlineDroppedRequest() 204 return nil 205 } 206 207 log.Debug("processing incoming request", "nodeID", nodeID, "requestID", requestID, "req", req) 208 ctx, cancel := context.WithDeadline(context.Background(), bufferedDeadline) 209 defer cancel() 210 211 responseBytes, err := req.Handle(ctx, nodeID, requestID, n.requestHandler) 212 switch { 213 case err != nil && err != context.DeadlineExceeded: 214 return err // Return a fatal error 215 case responseBytes != nil: 216 return n.appSender.SendAppResponse(nodeID, requestID, responseBytes) // Propagate fatal error 217 default: 218 return nil 219 } 220 } 221 222 // AppResponse is invoked when there is a response received from a peer regarding a request 223 // Error returned by this function is expected to be treated as fatal by the engine 224 // If [requestID] is not known, this function will emit a log and return a nil error. 225 // If the response handler returns an error it is propagated as a fatal error. 226 func (n *network) AppResponse(nodeID ids.NodeID, requestID uint32, response []byte) error { 227 n.lock.Lock() 228 defer n.lock.Unlock() 229 230 log.Debug("received AppResponse from peer", "nodeID", nodeID, "requestID", requestID) 231 232 handler, exists := n.getRequestHandler(requestID) 233 if !exists { 234 // Should never happen since the engine should be managing outstanding requests 235 log.Error("received response to unknown request", "nodeID", nodeID, "requestID", requestID, "responseLen", len(response)) 236 return nil 237 } 238 239 return handler.OnResponse(nodeID, requestID, response) 240 } 241 242 // AppRequestFailed can be called by the qmallgo -> VM in following cases: 243 // - node is benched 244 // - failed to send message to [nodeID] due to a network issue 245 // - timeout 246 // error returned by this function is expected to be treated as fatal by the engine 247 // returns error only when the response handler returns an error 248 func (n *network) AppRequestFailed(nodeID ids.NodeID, requestID uint32) error { 249 n.lock.Lock() 250 defer n.lock.Unlock() 251 log.Debug("received AppRequestFailed from peer", "nodeID", nodeID, "requestID", requestID) 252 253 handler, exists := n.getRequestHandler(requestID) 254 if !exists { 255 // Should never happen since the engine should be managing outstanding requests 256 log.Error("received request failed to unknown request", "nodeID", nodeID, "requestID", requestID) 257 return nil 258 } 259 260 return handler.OnFailure(nodeID, requestID) 261 } 262 263 // getRequestHandler fetches the handler for [requestID] and marks the request with [requestID] as having been fulfilled. 264 // This is called by either [AppResponse] or [AppRequestFailed]. 265 // assumes that the write lock is held. 266 func (n *network) getRequestHandler(requestID uint32) (message.ResponseHandler, bool) { 267 handler, exists := n.outstandingRequestHandlers[requestID] 268 if !exists { 269 return nil, false 270 } 271 // mark message as processed, release activeRequests slot 272 delete(n.outstandingRequestHandlers, requestID) 273 n.activeRequests.Release(1) 274 return handler, true 275 } 276 277 // Gossip sends given gossip message to peers 278 func (n *network) Gossip(gossip []byte) error { 279 return n.appSender.SendAppGossip(gossip) 280 } 281 282 // AppGossip is called by qmallgo -> VM when there is an incoming AppGossip from a peer 283 // error returned by this function is expected to be treated as fatal by the engine 284 // returns error if request could not be parsed as message.Request or when the requestHandler returns an error 285 func (n *network) AppGossip(nodeID ids.NodeID, gossipBytes []byte) error { 286 var gossipMsg message.GossipMessage 287 if _, err := n.codec.Unmarshal(gossipBytes, &gossipMsg); err != nil { 288 log.Debug("could not parse app gossip", "nodeID", nodeID, "gossipLen", len(gossipBytes), "err", err) 289 return nil 290 } 291 292 log.Debug("processing AppGossip from node", "nodeID", nodeID, "msg", gossipMsg) 293 return gossipMsg.Handle(n.gossipHandler, nodeID) 294 } 295 296 // Connected adds the given nodeID to the peer list so that it can receive messages 297 func (n *network) Connected(nodeID ids.NodeID, nodeVersion *version.Application) error { 298 log.Debug("adding new peer", "nodeID", nodeID) 299 300 n.lock.Lock() 301 defer n.lock.Unlock() 302 303 if nodeID == n.self { 304 log.Debug("skipping registering self as peer") 305 return nil 306 } 307 308 n.peers.Connected(nodeID, nodeVersion) 309 return nil 310 } 311 312 // Disconnected removes given [nodeID] from the peer list 313 func (n *network) Disconnected(nodeID ids.NodeID) error { 314 log.Debug("disconnecting peer", "nodeID", nodeID) 315 n.lock.Lock() 316 defer n.lock.Unlock() 317 318 n.peers.Disconnected(nodeID) 319 return nil 320 } 321 322 // Shutdown disconnects all peers 323 func (n *network) Shutdown() { 324 n.lock.Lock() 325 defer n.lock.Unlock() 326 327 // reset peers 328 n.peers = NewPeerTracker() 329 } 330 331 func (n *network) SetGossipHandler(handler message.GossipHandler) { 332 n.lock.Lock() 333 defer n.lock.Unlock() 334 335 n.gossipHandler = handler 336 } 337 338 func (n *network) SetRequestHandler(handler message.RequestHandler) { 339 n.lock.Lock() 340 defer n.lock.Unlock() 341 342 n.requestHandler = handler 343 } 344 345 func (n *network) Size() uint32 { 346 n.lock.RLock() 347 defer n.lock.RUnlock() 348 349 return uint32(n.peers.Size()) 350 } 351 352 func (n *network) TrackBandwidth(nodeID ids.NodeID, bandwidth float64) { 353 n.lock.Lock() 354 defer n.lock.Unlock() 355 356 n.peers.TrackBandwidth(nodeID, bandwidth) 357 }