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 }