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  }