github.com/MetalBlockchain/subnet-evm@v0.4.9/sync/client/client.go (about) 1 // (c) 2021-2022, Ava Labs, Inc. All rights reserved. 2 // See the file LICENSE for licensing terms. 3 4 package statesyncclient 5 6 import ( 7 "bytes" 8 "context" 9 "errors" 10 "fmt" 11 "sync/atomic" 12 "time" 13 14 "github.com/MetalBlockchain/metalgo/ids" 15 16 "github.com/MetalBlockchain/subnet-evm/ethdb/memorydb" 17 "github.com/MetalBlockchain/subnet-evm/params" 18 "github.com/MetalBlockchain/subnet-evm/sync/client/stats" 19 20 "github.com/MetalBlockchain/metalgo/codec" 21 "github.com/MetalBlockchain/metalgo/version" 22 23 "github.com/ethereum/go-ethereum/common" 24 "github.com/ethereum/go-ethereum/crypto" 25 "github.com/ethereum/go-ethereum/log" 26 27 "github.com/MetalBlockchain/subnet-evm/core/types" 28 "github.com/MetalBlockchain/subnet-evm/ethdb" 29 "github.com/MetalBlockchain/subnet-evm/peer" 30 "github.com/MetalBlockchain/subnet-evm/plugin/evm/message" 31 "github.com/MetalBlockchain/subnet-evm/trie" 32 ) 33 34 const ( 35 failedRequestSleepInterval = 10 * time.Millisecond 36 37 epsilon = 1e-6 // small amount to add to time to avoid division by 0 38 ) 39 40 var ( 41 StateSyncVersion = &version.Application{ 42 Major: 1, 43 Minor: 7, 44 Patch: 13, 45 } 46 errEmptyResponse = errors.New("empty response") 47 errTooManyBlocks = errors.New("response contains more blocks than requested") 48 errHashMismatch = errors.New("hash does not match expected value") 49 errInvalidRangeProof = errors.New("failed to verify range proof") 50 errTooManyLeaves = errors.New("response contains more than requested leaves") 51 errUnmarshalResponse = errors.New("failed to unmarshal response") 52 errInvalidCodeResponseLen = errors.New("number of code bytes in response does not match requested hashes") 53 errMaxCodeSizeExceeded = errors.New("max code size exceeded") 54 ) 55 var _ Client = &client{} 56 57 // Client synchronously fetches data from the network to fulfill state sync requests. 58 // Repeatedly requests failed requests until the context to the request is expired. 59 type Client interface { 60 // GetLeafs synchronously sends the given request, returning a parsed LeafsResponse or error 61 // Note: this verifies the response including the range proofs. 62 GetLeafs(ctx context.Context, request message.LeafsRequest) (message.LeafsResponse, error) 63 64 // GetBlocks synchronously retrieves blocks starting with specified common.Hash and height up to specified parents 65 // specified range from height to height-parents is inclusive 66 GetBlocks(ctx context.Context, blockHash common.Hash, height uint64, parents uint16) ([]*types.Block, error) 67 68 // GetCode synchronously retrieves code associated with the given hashes 69 GetCode(ctx context.Context, hashes []common.Hash) ([][]byte, error) 70 } 71 72 // parseResponseFn parses given response bytes in context of specified request 73 // Validates response in context of the request 74 // Ensures the returned interface matches the expected response type of the request 75 // Returns the number of elements in the response (specific to the response type, used in metrics) 76 type parseResponseFn func(codec codec.Manager, request message.Request, response []byte) (interface{}, int, error) 77 78 type client struct { 79 networkClient peer.NetworkClient 80 codec codec.Manager 81 stateSyncNodes []ids.NodeID 82 stateSyncNodeIdx uint32 83 stats stats.ClientSyncerStats 84 blockParser EthBlockParser 85 } 86 87 type ClientConfig struct { 88 NetworkClient peer.NetworkClient 89 Codec codec.Manager 90 Stats stats.ClientSyncerStats 91 StateSyncNodeIDs []ids.NodeID 92 BlockParser EthBlockParser 93 } 94 95 type EthBlockParser interface { 96 ParseEthBlock(b []byte) (*types.Block, error) 97 } 98 99 func NewClient(config *ClientConfig) *client { 100 return &client{ 101 networkClient: config.NetworkClient, 102 codec: config.Codec, 103 stats: config.Stats, 104 stateSyncNodes: config.StateSyncNodeIDs, 105 blockParser: config.BlockParser, 106 } 107 } 108 109 // GetLeafs synchronously retrieves leafs as per given [message.LeafsRequest] 110 // Retries when: 111 // - response bytes could not be unmarshalled to [message.LeafsResponse] 112 // - response keys do not correspond to the requested range. 113 // - response does not contain a valid merkle proof. 114 func (c *client) GetLeafs(ctx context.Context, req message.LeafsRequest) (message.LeafsResponse, error) { 115 data, err := c.get(ctx, req, parseLeafsResponse) 116 if err != nil { 117 return message.LeafsResponse{}, err 118 } 119 120 return data.(message.LeafsResponse), nil 121 } 122 123 // parseLeafsResponse validates given object as message.LeafsResponse 124 // assumes reqIntf is of type message.LeafsRequest 125 // returns a non-nil error if the request should be retried 126 // returns error when: 127 // - response bytes could not be unmarshalled into message.LeafsResponse 128 // - number of response keys is not equal to the response values 129 // - first and last key in the response is not within the requested start and end range 130 // - response keys are not in increasing order 131 // - proof validation failed 132 func parseLeafsResponse(codec codec.Manager, reqIntf message.Request, data []byte) (interface{}, int, error) { 133 var leafsResponse message.LeafsResponse 134 if _, err := codec.Unmarshal(data, &leafsResponse); err != nil { 135 return nil, 0, err 136 } 137 138 leafsRequest := reqIntf.(message.LeafsRequest) 139 140 // Ensure the response does not contain more than the maximum requested number of leaves. 141 if len(leafsResponse.Keys) > int(leafsRequest.Limit) || len(leafsResponse.Vals) > int(leafsRequest.Limit) { 142 return nil, 0, fmt.Errorf("%w: (%d) > %d)", errTooManyLeaves, len(leafsResponse.Keys), leafsRequest.Limit) 143 } 144 145 // An empty response (no more keys) requires a merkle proof 146 if len(leafsResponse.Keys) == 0 && len(leafsResponse.ProofVals) == 0 { 147 return nil, 0, fmt.Errorf("empty key response must include merkle proof") 148 } 149 150 var proof ethdb.Database 151 // Populate proof when ProofVals are present in the response. Its ok to pass it as nil to the trie.VerifyRangeProof 152 // function as it will assert that all the leaves belonging to the specified root are present. 153 if len(leafsResponse.ProofVals) > 0 { 154 proof = memorydb.New() 155 defer proof.Close() 156 for _, proofVal := range leafsResponse.ProofVals { 157 proofKey := crypto.Keccak256(proofVal) 158 if err := proof.Put(proofKey, proofVal); err != nil { 159 return nil, 0, err 160 } 161 } 162 } 163 164 var ( 165 firstKey = leafsRequest.Start 166 lastKey = leafsRequest.End 167 ) 168 // Last key is the last returned key in response 169 if len(leafsResponse.Keys) > 0 { 170 lastKey = leafsResponse.Keys[len(leafsResponse.Keys)-1] 171 172 if firstKey == nil { 173 firstKey = bytes.Repeat([]byte{0x00}, len(lastKey)) 174 } 175 } 176 177 // VerifyRangeProof verifies that the key-value pairs included in [leafResponse] are all of the keys within the range from start 178 // to the last key returned. 179 // Also ensures the keys are in monotonically increasing order 180 more, err := trie.VerifyRangeProof(leafsRequest.Root, firstKey, lastKey, leafsResponse.Keys, leafsResponse.Vals, proof) 181 if err != nil { 182 return nil, 0, fmt.Errorf("%s due to %w", errInvalidRangeProof, err) 183 } 184 185 // Set the [More] flag to indicate if there are more leaves to the right of the last key in the response 186 // that needs to be fetched. 187 leafsResponse.More = more 188 189 return leafsResponse, len(leafsResponse.Keys), nil 190 } 191 192 func (c *client) GetBlocks(ctx context.Context, hash common.Hash, height uint64, parents uint16) ([]*types.Block, error) { 193 req := message.BlockRequest{ 194 Hash: hash, 195 Height: height, 196 Parents: parents, 197 } 198 199 data, err := c.get(ctx, req, c.parseBlocks) 200 if err != nil { 201 return nil, fmt.Errorf("could not get blocks (%s) due to %w", hash, err) 202 } 203 204 return data.(types.Blocks), nil 205 } 206 207 // parseBlocks validates given object as message.BlockResponse 208 // assumes req is of type message.BlockRequest 209 // returns types.Blocks as interface{} 210 // returns a non-nil error if the request should be retried 211 func (c *client) parseBlocks(codec codec.Manager, req message.Request, data []byte) (interface{}, int, error) { 212 var response message.BlockResponse 213 if _, err := codec.Unmarshal(data, &response); err != nil { 214 return nil, 0, fmt.Errorf("%s: %w", errUnmarshalResponse, err) 215 } 216 if len(response.Blocks) == 0 { 217 return nil, 0, errEmptyResponse 218 } 219 blockRequest := req.(message.BlockRequest) 220 numParentsRequested := blockRequest.Parents 221 if len(response.Blocks) > int(numParentsRequested) { 222 return nil, 0, errTooManyBlocks 223 } 224 225 hash := blockRequest.Hash 226 227 // attempt to decode blocks 228 blocks := make(types.Blocks, len(response.Blocks)) 229 for i, blkBytes := range response.Blocks { 230 block, err := c.blockParser.ParseEthBlock(blkBytes) 231 if err != nil { 232 return nil, 0, fmt.Errorf("%s: %w", errUnmarshalResponse, err) 233 } 234 235 if block.Hash() != hash { 236 return nil, 0, fmt.Errorf("%w for block: (got %v) (expected %v)", errHashMismatch, block.Hash(), hash) 237 } 238 239 blocks[i] = block 240 hash = block.ParentHash() 241 } 242 243 // return decoded blocks 244 return blocks, len(blocks), nil 245 } 246 247 func (c *client) GetCode(ctx context.Context, hashes []common.Hash) ([][]byte, error) { 248 req := message.NewCodeRequest(hashes) 249 250 data, err := c.get(ctx, req, parseCode) 251 if err != nil { 252 return nil, fmt.Errorf("could not get code (%s): %w", req, err) 253 } 254 255 return data.([][]byte), nil 256 } 257 258 // parseCode validates given object as a code object 259 // assumes req is of type message.CodeRequest 260 // returns a non-nil error if the request should be retried 261 func parseCode(codec codec.Manager, req message.Request, data []byte) (interface{}, int, error) { 262 var response message.CodeResponse 263 if _, err := codec.Unmarshal(data, &response); err != nil { 264 return nil, 0, err 265 } 266 267 codeRequest := req.(message.CodeRequest) 268 if len(response.Data) != len(codeRequest.Hashes) { 269 return nil, 0, fmt.Errorf("%w (got %d) (requested %d)", errInvalidCodeResponseLen, len(response.Data), len(codeRequest.Hashes)) 270 } 271 272 totalBytes := 0 273 for i, code := range response.Data { 274 if len(code) > params.MaxCodeSize { 275 return nil, 0, fmt.Errorf("%w: (hash %s) (size %d)", errMaxCodeSizeExceeded, codeRequest.Hashes[i], len(code)) 276 } 277 278 hash := crypto.Keccak256Hash(code) 279 if hash != codeRequest.Hashes[i] { 280 return nil, 0, fmt.Errorf("%w for code at index %d: (got %v) (expected %v)", errHashMismatch, i, hash, codeRequest.Hashes[i]) 281 } 282 totalBytes += len(code) 283 } 284 285 return response.Data, totalBytes, nil 286 } 287 288 // get submits given request and blockingly returns with either a parsed response object or an error 289 // if [ctx] expires before the client can successfully retrieve a valid response. 290 // Retries if there is a network error or if the [parseResponseFn] returns an error indicating an invalid response. 291 // Returns the parsed interface returned from [parseFn]. 292 // Thread safe 293 func (c *client) get(ctx context.Context, request message.Request, parseFn parseResponseFn) (interface{}, error) { 294 // marshal the request into requestBytes 295 requestBytes, err := message.RequestToBytes(c.codec, request) 296 if err != nil { 297 return nil, err 298 } 299 300 metric, err := c.stats.GetMetric(request) 301 if err != nil { 302 return nil, err 303 } 304 var ( 305 responseIntf interface{} 306 numElements int 307 lastErr error 308 ) 309 // Loop until the context is cancelled or we get a valid response. 310 for attempt := 0; ; attempt++ { 311 // If the context has finished, return the context error early. 312 if ctxErr := ctx.Err(); ctxErr != nil { 313 if lastErr != nil { 314 return nil, fmt.Errorf("request failed after %d attempts with last error %w and ctx error %s", attempt, lastErr, ctxErr) 315 } else { 316 return nil, ctxErr 317 } 318 } 319 320 metric.IncRequested() 321 322 var ( 323 response []byte 324 nodeID ids.NodeID 325 start time.Time = time.Now() 326 ) 327 if len(c.stateSyncNodes) == 0 { 328 response, nodeID, err = c.networkClient.SendAppRequestAny(StateSyncVersion, requestBytes) 329 } else { 330 // get the next nodeID using the nodeIdx offset. If we're out of nodes, loop back to 0 331 // we do this every attempt to ensure we get a different node each time if possible. 332 nodeIdx := atomic.AddUint32(&c.stateSyncNodeIdx, 1) 333 nodeID = c.stateSyncNodes[nodeIdx%uint32(len(c.stateSyncNodes))] 334 335 response, err = c.networkClient.SendAppRequest(nodeID, requestBytes) 336 } 337 metric.UpdateRequestLatency(time.Since(start)) 338 339 if err != nil { 340 ctx := make([]interface{}, 0, 8) 341 if nodeID != ids.EmptyNodeID { 342 ctx = append(ctx, "nodeID", nodeID) 343 } 344 ctx = append(ctx, "attempt", attempt, "request", request, "err", err) 345 log.Debug("request failed, retrying", ctx...) 346 metric.IncFailed() 347 c.networkClient.TrackBandwidth(nodeID, 0) 348 time.Sleep(failedRequestSleepInterval) 349 continue 350 } else { 351 responseIntf, numElements, err = parseFn(c.codec, request, response) 352 if err != nil { 353 lastErr = err 354 log.Info("could not validate response, retrying", "nodeID", nodeID, "attempt", attempt, "request", request, "err", err) 355 c.networkClient.TrackBandwidth(nodeID, 0) 356 metric.IncFailed() 357 metric.IncInvalidResponse() 358 continue 359 } 360 361 bandwidth := float64(len(response)) / (time.Since(start).Seconds() + epsilon) 362 c.networkClient.TrackBandwidth(nodeID, bandwidth) 363 metric.IncSucceeded() 364 metric.IncReceived(int64(numElements)) 365 return responseIntf, nil 366 } 367 } 368 }