github.com/MetalBlockchain/metalgo@v1.11.9/network/p2p/router.go (about) 1 // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. 2 // See the file LICENSE for licensing terms. 3 4 package p2p 5 6 import ( 7 "context" 8 "encoding/binary" 9 "errors" 10 "fmt" 11 "strconv" 12 "sync" 13 "time" 14 15 "github.com/prometheus/client_golang/prometheus" 16 "go.uber.org/zap" 17 18 "github.com/MetalBlockchain/metalgo/ids" 19 "github.com/MetalBlockchain/metalgo/message" 20 "github.com/MetalBlockchain/metalgo/snow/engine/common" 21 "github.com/MetalBlockchain/metalgo/utils/logging" 22 ) 23 24 var ( 25 ErrExistingAppProtocol = errors.New("existing app protocol") 26 ErrUnrequestedResponse = errors.New("unrequested response") 27 28 _ common.AppHandler = (*router)(nil) 29 ) 30 31 type pendingAppRequest struct { 32 handlerID string 33 callback AppResponseCallback 34 } 35 36 type pendingCrossChainAppRequest struct { 37 handlerID string 38 callback CrossChainAppResponseCallback 39 } 40 41 // meteredHandler emits metrics for a Handler 42 type meteredHandler struct { 43 *responder 44 metrics 45 } 46 47 type metrics struct { 48 msgTime *prometheus.GaugeVec 49 msgCount *prometheus.CounterVec 50 } 51 52 func (m *metrics) observe(labels prometheus.Labels, start time.Time) error { 53 metricTime, err := m.msgTime.GetMetricWith(labels) 54 if err != nil { 55 return err 56 } 57 58 metricCount, err := m.msgCount.GetMetricWith(labels) 59 if err != nil { 60 return err 61 } 62 63 metricTime.Add(float64(time.Since(start))) 64 metricCount.Inc() 65 return nil 66 } 67 68 // router routes incoming application messages to the corresponding registered 69 // app handler. App messages must be made using the registered handler's 70 // corresponding Client. 71 type router struct { 72 log logging.Logger 73 sender common.AppSender 74 metrics metrics 75 76 lock sync.RWMutex 77 handlers map[uint64]*meteredHandler 78 pendingAppRequests map[uint32]pendingAppRequest 79 pendingCrossChainAppRequests map[uint32]pendingCrossChainAppRequest 80 requestID uint32 81 } 82 83 // newRouter returns a new instance of Router 84 func newRouter( 85 log logging.Logger, 86 sender common.AppSender, 87 metrics metrics, 88 ) *router { 89 return &router{ 90 log: log, 91 sender: sender, 92 metrics: metrics, 93 handlers: make(map[uint64]*meteredHandler), 94 pendingAppRequests: make(map[uint32]pendingAppRequest), 95 pendingCrossChainAppRequests: make(map[uint32]pendingCrossChainAppRequest), 96 // invariant: sdk uses odd-numbered requestIDs 97 requestID: 1, 98 } 99 } 100 101 func (r *router) addHandler(handlerID uint64, handler Handler) error { 102 r.lock.Lock() 103 defer r.lock.Unlock() 104 105 if _, ok := r.handlers[handlerID]; ok { 106 return fmt.Errorf("failed to register handler id %d: %w", handlerID, ErrExistingAppProtocol) 107 } 108 109 r.handlers[handlerID] = &meteredHandler{ 110 responder: &responder{ 111 Handler: handler, 112 handlerID: handlerID, 113 log: r.log, 114 sender: r.sender, 115 }, 116 metrics: r.metrics, 117 } 118 119 return nil 120 } 121 122 // AppRequest routes an AppRequest to a Handler based on the handler prefix. The 123 // message is dropped if no matching handler can be found. 124 // 125 // Any error condition propagated outside Handler application logic is 126 // considered fatal 127 func (r *router) AppRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, deadline time.Time, request []byte) error { 128 start := time.Now() 129 parsedMsg, handler, handlerID, ok := r.parse(request) 130 if !ok { 131 r.log.Debug("failed to process message", 132 zap.Stringer("messageOp", message.AppRequestOp), 133 zap.Stringer("nodeID", nodeID), 134 zap.Uint32("requestID", requestID), 135 zap.Time("deadline", deadline), 136 zap.Binary("message", request), 137 ) 138 return nil 139 } 140 141 // call the corresponding handler and send back a response to nodeID 142 if err := handler.AppRequest(ctx, nodeID, requestID, deadline, parsedMsg); err != nil { 143 return err 144 } 145 146 return r.metrics.observe( 147 prometheus.Labels{ 148 opLabel: message.AppRequestOp.String(), 149 handlerLabel: handlerID, 150 }, 151 start, 152 ) 153 } 154 155 // AppRequestFailed routes an AppRequestFailed message to the callback 156 // corresponding to requestID. 157 // 158 // Any error condition propagated outside Handler application logic is 159 // considered fatal 160 func (r *router) AppRequestFailed(ctx context.Context, nodeID ids.NodeID, requestID uint32, appErr *common.AppError) error { 161 start := time.Now() 162 pending, ok := r.clearAppRequest(requestID) 163 if !ok { 164 // we should never receive a timeout without a corresponding requestID 165 return ErrUnrequestedResponse 166 } 167 168 pending.callback(ctx, nodeID, nil, appErr) 169 170 return r.metrics.observe( 171 prometheus.Labels{ 172 opLabel: message.AppErrorOp.String(), 173 handlerLabel: pending.handlerID, 174 }, 175 start, 176 ) 177 } 178 179 // AppResponse routes an AppResponse message to the callback corresponding to 180 // requestID. 181 // 182 // Any error condition propagated outside Handler application logic is 183 // considered fatal 184 func (r *router) AppResponse(ctx context.Context, nodeID ids.NodeID, requestID uint32, response []byte) error { 185 start := time.Now() 186 pending, ok := r.clearAppRequest(requestID) 187 if !ok { 188 // we should never receive a timeout without a corresponding requestID 189 return ErrUnrequestedResponse 190 } 191 192 pending.callback(ctx, nodeID, response, nil) 193 194 return r.metrics.observe( 195 prometheus.Labels{ 196 opLabel: message.AppResponseOp.String(), 197 handlerLabel: pending.handlerID, 198 }, 199 start, 200 ) 201 } 202 203 // AppGossip routes an AppGossip message to a Handler based on the handler 204 // prefix. The message is dropped if no matching handler can be found. 205 // 206 // Any error condition propagated outside Handler application logic is 207 // considered fatal 208 func (r *router) AppGossip(ctx context.Context, nodeID ids.NodeID, gossip []byte) error { 209 start := time.Now() 210 parsedMsg, handler, handlerID, ok := r.parse(gossip) 211 if !ok { 212 r.log.Debug("failed to process message", 213 zap.Stringer("messageOp", message.AppGossipOp), 214 zap.Stringer("nodeID", nodeID), 215 zap.Binary("message", gossip), 216 ) 217 return nil 218 } 219 220 handler.AppGossip(ctx, nodeID, parsedMsg) 221 222 return r.metrics.observe( 223 prometheus.Labels{ 224 opLabel: message.AppGossipOp.String(), 225 handlerLabel: handlerID, 226 }, 227 start, 228 ) 229 } 230 231 // CrossChainAppRequest routes a CrossChainAppRequest message to a Handler 232 // based on the handler prefix. The message is dropped if no matching handler 233 // can be found. 234 // 235 // Any error condition propagated outside Handler application logic is 236 // considered fatal 237 func (r *router) CrossChainAppRequest( 238 ctx context.Context, 239 chainID ids.ID, 240 requestID uint32, 241 deadline time.Time, 242 msg []byte, 243 ) error { 244 start := time.Now() 245 parsedMsg, handler, handlerID, ok := r.parse(msg) 246 if !ok { 247 r.log.Debug("failed to process message", 248 zap.Stringer("messageOp", message.CrossChainAppRequestOp), 249 zap.Stringer("chainID", chainID), 250 zap.Uint32("requestID", requestID), 251 zap.Time("deadline", deadline), 252 zap.Binary("message", msg), 253 ) 254 return nil 255 } 256 257 if err := handler.CrossChainAppRequest(ctx, chainID, requestID, deadline, parsedMsg); err != nil { 258 return err 259 } 260 261 return r.metrics.observe( 262 prometheus.Labels{ 263 opLabel: message.CrossChainAppRequestOp.String(), 264 handlerLabel: handlerID, 265 }, 266 start, 267 ) 268 } 269 270 // CrossChainAppRequestFailed routes a CrossChainAppRequestFailed message to 271 // the callback corresponding to requestID. 272 // 273 // Any error condition propagated outside Handler application logic is 274 // considered fatal 275 func (r *router) CrossChainAppRequestFailed(ctx context.Context, chainID ids.ID, requestID uint32, appErr *common.AppError) error { 276 start := time.Now() 277 pending, ok := r.clearCrossChainAppRequest(requestID) 278 if !ok { 279 // we should never receive a timeout without a corresponding requestID 280 return ErrUnrequestedResponse 281 } 282 283 pending.callback(ctx, chainID, nil, appErr) 284 285 return r.metrics.observe( 286 prometheus.Labels{ 287 opLabel: message.CrossChainAppErrorOp.String(), 288 handlerLabel: pending.handlerID, 289 }, 290 start, 291 ) 292 } 293 294 // CrossChainAppResponse routes a CrossChainAppResponse message to the callback 295 // corresponding to requestID. 296 // 297 // Any error condition propagated outside Handler application logic is 298 // considered fatal 299 func (r *router) CrossChainAppResponse(ctx context.Context, chainID ids.ID, requestID uint32, response []byte) error { 300 start := time.Now() 301 pending, ok := r.clearCrossChainAppRequest(requestID) 302 if !ok { 303 // we should never receive a timeout without a corresponding requestID 304 return ErrUnrequestedResponse 305 } 306 307 pending.callback(ctx, chainID, response, nil) 308 309 return r.metrics.observe( 310 prometheus.Labels{ 311 opLabel: message.CrossChainAppResponseOp.String(), 312 handlerLabel: pending.handlerID, 313 }, 314 start, 315 ) 316 } 317 318 // Parse parses a gossip or request message and maps it to a corresponding 319 // handler if present. 320 // 321 // Returns: 322 // - The unprefixed protocol message. 323 // - The protocol responder. 324 // - The protocol metric name. 325 // - A boolean indicating that parsing succeeded. 326 // 327 // Invariant: Assumes [r.lock] isn't held. 328 func (r *router) parse(prefixedMsg []byte) ([]byte, *meteredHandler, string, bool) { 329 handlerID, msg, ok := ParseMessage(prefixedMsg) 330 if !ok { 331 return nil, nil, "", false 332 } 333 334 handlerStr := strconv.FormatUint(handlerID, 10) 335 336 r.lock.RLock() 337 defer r.lock.RUnlock() 338 339 handler, ok := r.handlers[handlerID] 340 return msg, handler, handlerStr, ok 341 } 342 343 // Invariant: Assumes [r.lock] isn't held. 344 func (r *router) clearAppRequest(requestID uint32) (pendingAppRequest, bool) { 345 r.lock.Lock() 346 defer r.lock.Unlock() 347 348 callback, ok := r.pendingAppRequests[requestID] 349 delete(r.pendingAppRequests, requestID) 350 return callback, ok 351 } 352 353 // Invariant: Assumes [r.lock] isn't held. 354 func (r *router) clearCrossChainAppRequest(requestID uint32) (pendingCrossChainAppRequest, bool) { 355 r.lock.Lock() 356 defer r.lock.Unlock() 357 358 callback, ok := r.pendingCrossChainAppRequests[requestID] 359 delete(r.pendingCrossChainAppRequests, requestID) 360 return callback, ok 361 } 362 363 // Parse a gossip or request message. 364 // 365 // Returns: 366 // - The protocol ID. 367 // - The unprefixed protocol message. 368 // - A boolean indicating that parsing succeeded. 369 func ParseMessage(msg []byte) (uint64, []byte, bool) { 370 handlerID, bytesRead := binary.Uvarint(msg) 371 if bytesRead <= 0 { 372 return 0, nil, false 373 } 374 return handlerID, msg[bytesRead:], true 375 }