github.com/MetalBlockchain/metalgo@v1.11.9/network/p2p/client.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  	"errors"
     9  	"fmt"
    10  
    11  	"go.uber.org/zap"
    12  
    13  	"github.com/MetalBlockchain/metalgo/ids"
    14  	"github.com/MetalBlockchain/metalgo/message"
    15  	"github.com/MetalBlockchain/metalgo/snow/engine/common"
    16  	"github.com/MetalBlockchain/metalgo/utils/set"
    17  )
    18  
    19  var (
    20  	ErrRequestPending = errors.New("request pending")
    21  	ErrNoPeers        = errors.New("no peers")
    22  )
    23  
    24  // AppResponseCallback is called upon receiving an AppResponse for an AppRequest
    25  // issued by Client.
    26  // Callers should check [err] to see whether the AppRequest failed or not.
    27  type AppResponseCallback func(
    28  	ctx context.Context,
    29  	nodeID ids.NodeID,
    30  	responseBytes []byte,
    31  	err error,
    32  )
    33  
    34  // CrossChainAppResponseCallback is called upon receiving an
    35  // CrossChainAppResponse for a CrossChainAppRequest issued by Client.
    36  // Callers should check [err] to see whether the AppRequest failed or not.
    37  type CrossChainAppResponseCallback func(
    38  	ctx context.Context,
    39  	chainID ids.ID,
    40  	responseBytes []byte,
    41  	err error,
    42  )
    43  
    44  type Client struct {
    45  	handlerID     uint64
    46  	handlerIDStr  string
    47  	handlerPrefix []byte
    48  	router        *router
    49  	sender        common.AppSender
    50  	options       *clientOptions
    51  }
    52  
    53  // AppRequestAny issues an AppRequest to an arbitrary node decided by Client.
    54  // If a specific node needs to be requested, use AppRequest instead.
    55  // See AppRequest for more docs.
    56  func (c *Client) AppRequestAny(
    57  	ctx context.Context,
    58  	appRequestBytes []byte,
    59  	onResponse AppResponseCallback,
    60  ) error {
    61  	sampled := c.options.nodeSampler.Sample(ctx, 1)
    62  	if len(sampled) != 1 {
    63  		return ErrNoPeers
    64  	}
    65  
    66  	nodeIDs := set.Of(sampled...)
    67  	return c.AppRequest(ctx, nodeIDs, appRequestBytes, onResponse)
    68  }
    69  
    70  // AppRequest issues an arbitrary request to a node.
    71  // [onResponse] is invoked upon an error or a response.
    72  func (c *Client) AppRequest(
    73  	ctx context.Context,
    74  	nodeIDs set.Set[ids.NodeID],
    75  	appRequestBytes []byte,
    76  	onResponse AppResponseCallback,
    77  ) error {
    78  	// Cancellation is removed from this context to avoid erroring unexpectedly.
    79  	// SendAppRequest should be non-blocking and any error other than context
    80  	// cancellation is unexpected.
    81  	//
    82  	// This guarantees that the router should never receive an unexpected
    83  	// AppResponse.
    84  	ctxWithoutCancel := context.WithoutCancel(ctx)
    85  
    86  	c.router.lock.Lock()
    87  	defer c.router.lock.Unlock()
    88  
    89  	appRequestBytes = PrefixMessage(c.handlerPrefix, appRequestBytes)
    90  	for nodeID := range nodeIDs {
    91  		requestID := c.router.requestID
    92  		if _, ok := c.router.pendingAppRequests[requestID]; ok {
    93  			return fmt.Errorf(
    94  				"failed to issue request with request id %d: %w",
    95  				requestID,
    96  				ErrRequestPending,
    97  			)
    98  		}
    99  
   100  		if err := c.sender.SendAppRequest(
   101  			ctxWithoutCancel,
   102  			set.Of(nodeID),
   103  			requestID,
   104  			appRequestBytes,
   105  		); err != nil {
   106  			c.router.log.Error("unexpected error when sending message",
   107  				zap.Stringer("op", message.AppRequestOp),
   108  				zap.Stringer("nodeID", nodeID),
   109  				zap.Uint32("requestID", requestID),
   110  				zap.Error(err),
   111  			)
   112  			return err
   113  		}
   114  
   115  		c.router.pendingAppRequests[requestID] = pendingAppRequest{
   116  			handlerID: c.handlerIDStr,
   117  			callback:  onResponse,
   118  		}
   119  		c.router.requestID += 2
   120  	}
   121  
   122  	return nil
   123  }
   124  
   125  // AppGossip sends a gossip message to a random set of peers.
   126  func (c *Client) AppGossip(
   127  	ctx context.Context,
   128  	config common.SendConfig,
   129  	appGossipBytes []byte,
   130  ) error {
   131  	// Cancellation is removed from this context to avoid erroring unexpectedly.
   132  	// SendAppGossip should be non-blocking and any error other than context
   133  	// cancellation is unexpected.
   134  	ctxWithoutCancel := context.WithoutCancel(ctx)
   135  
   136  	return c.sender.SendAppGossip(
   137  		ctxWithoutCancel,
   138  		config,
   139  		PrefixMessage(c.handlerPrefix, appGossipBytes),
   140  	)
   141  }
   142  
   143  // CrossChainAppRequest sends a cross chain app request to another vm.
   144  // [onResponse] is invoked upon an error or a response.
   145  func (c *Client) CrossChainAppRequest(
   146  	ctx context.Context,
   147  	chainID ids.ID,
   148  	appRequestBytes []byte,
   149  	onResponse CrossChainAppResponseCallback,
   150  ) error {
   151  	// Cancellation is removed from this context to avoid erroring unexpectedly.
   152  	// SendCrossChainAppRequest should be non-blocking and any error other than
   153  	// context cancellation is unexpected.
   154  	//
   155  	// This guarantees that the router should never receive an unexpected
   156  	// CrossChainAppResponse.
   157  	ctxWithoutCancel := context.WithoutCancel(ctx)
   158  
   159  	c.router.lock.Lock()
   160  	defer c.router.lock.Unlock()
   161  
   162  	requestID := c.router.requestID
   163  	if _, ok := c.router.pendingCrossChainAppRequests[requestID]; ok {
   164  		return fmt.Errorf(
   165  			"failed to issue request with request id %d: %w",
   166  			requestID,
   167  			ErrRequestPending,
   168  		)
   169  	}
   170  
   171  	if err := c.sender.SendCrossChainAppRequest(
   172  		ctxWithoutCancel,
   173  		chainID,
   174  		requestID,
   175  		PrefixMessage(c.handlerPrefix, appRequestBytes),
   176  	); err != nil {
   177  		c.router.log.Error("unexpected error when sending message",
   178  			zap.Stringer("op", message.CrossChainAppRequestOp),
   179  			zap.Stringer("chainID", chainID),
   180  			zap.Uint32("requestID", requestID),
   181  			zap.Error(err),
   182  		)
   183  		return err
   184  	}
   185  
   186  	c.router.pendingCrossChainAppRequests[requestID] = pendingCrossChainAppRequest{
   187  		handlerID: c.handlerIDStr,
   188  		callback:  onResponse,
   189  	}
   190  	c.router.requestID += 2
   191  
   192  	return nil
   193  }
   194  
   195  // PrefixMessage prefixes the original message with the protocol identifier.
   196  //
   197  // Only gossip and request messages need to be prefixed.
   198  // Response messages don't need to be prefixed because request ids are tracked
   199  // which map to the expected response handler.
   200  func PrefixMessage(prefix, msg []byte) []byte {
   201  	messageBytes := make([]byte, len(prefix)+len(msg))
   202  	copy(messageBytes, prefix)
   203  	copy(messageBytes[len(prefix):], msg)
   204  	return messageBytes
   205  }