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  }