github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/admin/commands/storage/helper.go (about)

     1  package storage
     2  
     3  import (
     4  	"fmt"
     5  	"math"
     6  	"strings"
     7  
     8  	"github.com/onflow/flow-go/admin"
     9  	"github.com/onflow/flow-go/model/flow"
    10  	"github.com/onflow/flow-go/state/protocol"
    11  )
    12  
    13  type blocksRequestType int
    14  
    15  const (
    16  	blocksRequestByID blocksRequestType = iota
    17  	blocksRequestByHeight
    18  	blocksRequestFinal
    19  	blocksRequestSealed
    20  )
    21  
    22  const (
    23  	FINAL  = "final"
    24  	SEALED = "sealed"
    25  )
    26  
    27  type blocksRequest struct {
    28  	requestType blocksRequestType
    29  	value       interface{}
    30  }
    31  
    32  // parseN verifies that the input is an integral float64 value >=1.
    33  // All generic errors indicate a benign validation failure, and should be wrapped by the caller.
    34  func parseN(m interface{}) (uint64, error) {
    35  	n, ok := m.(float64)
    36  	if !ok {
    37  		return 0, fmt.Errorf("invalid value for \"n\": %v", n)
    38  	}
    39  	if math.Trunc(n) != n {
    40  		return 0, fmt.Errorf("\"n\" must be an integer, got: %v", n)
    41  	}
    42  	if n < 1 {
    43  		return 0, fmt.Errorf("\"n\" must be at least 1, got: %v", n)
    44  	}
    45  	return uint64(n), nil
    46  }
    47  
    48  // parseBlocksRequest parses the block field of an admin request.
    49  // All generic errors indicate a benign validation failure, and should be wrapped by the caller.
    50  func parseBlocksRequest(block interface{}) (*blocksRequest, error) {
    51  	errInvalidBlockValue := fmt.Errorf("invalid value for \"block\": expected %q, %q, block ID, or block height, but got: %v", FINAL, SEALED, block)
    52  	req := &blocksRequest{}
    53  
    54  	switch block := block.(type) {
    55  	case string:
    56  		block = strings.ToLower(strings.TrimSpace(block))
    57  		if block == FINAL {
    58  			req.requestType = blocksRequestFinal
    59  		} else if block == SEALED {
    60  			req.requestType = blocksRequestSealed
    61  		} else if id, err := flow.HexStringToIdentifier(block); err == nil {
    62  			req.requestType = blocksRequestByID
    63  			req.value = id
    64  		} else {
    65  			return nil, errInvalidBlockValue
    66  		}
    67  	case float64:
    68  		if block < 0 || math.Trunc(block) != block {
    69  			return nil, errInvalidBlockValue
    70  		}
    71  		req.requestType = blocksRequestByHeight
    72  		req.value = uint64(block)
    73  	default:
    74  		return nil, errInvalidBlockValue
    75  	}
    76  
    77  	return req, nil
    78  }
    79  
    80  func getBlockHeader(state protocol.State, req *blocksRequest) (*flow.Header, error) {
    81  	switch req.requestType {
    82  	case blocksRequestByID:
    83  		return state.AtBlockID(req.value.(flow.Identifier)).Head()
    84  	case blocksRequestByHeight:
    85  		return state.AtHeight(req.value.(uint64)).Head()
    86  	case blocksRequestFinal:
    87  		return state.Final().Head()
    88  	case blocksRequestSealed:
    89  		return state.Sealed().Head()
    90  	default:
    91  		return nil, fmt.Errorf("invalid request type: %v", req.requestType)
    92  	}
    93  }
    94  
    95  func parseHeightRangeRequestData(req *admin.CommandRequest) (*heightRangeReqData, error) {
    96  	input, ok := req.Data.(map[string]interface{})
    97  	if !ok {
    98  		return nil, admin.NewInvalidAdminReqFormatError("missing 'data' field")
    99  	}
   100  
   101  	startHeight, err := findUint64(input, "start-height")
   102  	if err != nil {
   103  		return nil, fmt.Errorf("invalid start-height: %w", err)
   104  	}
   105  
   106  	endHeight, err := findUint64(input, "end-height")
   107  	if err != nil {
   108  		return nil, fmt.Errorf("invalid end-height: %w", err)
   109  	}
   110  
   111  	if endHeight < startHeight {
   112  		return nil, admin.NewInvalidAdminReqErrorf("end-height %v should not be smaller than start-height %v", endHeight, startHeight)
   113  	}
   114  
   115  	return &heightRangeReqData{
   116  		startHeight: startHeight,
   117  		endHeight:   endHeight,
   118  	}, nil
   119  }
   120  
   121  func parseString(req *admin.CommandRequest, field string) (string, error) {
   122  	input, ok := req.Data.(map[string]interface{})
   123  	if !ok {
   124  		return "", admin.NewInvalidAdminReqFormatError("missing 'data' field")
   125  	}
   126  	fieldValue, err := findString(input, field)
   127  	if err != nil {
   128  		return "", admin.NewInvalidAdminReqErrorf("missing %v field", field)
   129  	}
   130  	return fieldValue, nil
   131  }
   132  
   133  // Returns admin.InvalidAdminReqError for invalid inputs
   134  func findUint64(input map[string]interface{}, field string) (uint64, error) {
   135  	data, ok := input[field]
   136  	if !ok {
   137  		return 0, admin.NewInvalidAdminReqErrorf("missing required field '%s'", field)
   138  	}
   139  	val, err := parseN(data)
   140  	if err != nil {
   141  		return 0, admin.NewInvalidAdminReqErrorf("invalid 'n' field: %w", err)
   142  	}
   143  
   144  	return uint64(val), nil
   145  }
   146  
   147  func findString(input map[string]interface{}, field string) (string, error) {
   148  	data, ok := input[field]
   149  	if !ok {
   150  		return "", admin.NewInvalidAdminReqErrorf("missing required field '%s'", field)
   151  	}
   152  
   153  	str, ok := data.(string)
   154  	if !ok {
   155  		return "", admin.NewInvalidAdminReqErrorf("field '%s' is not string", field)
   156  	}
   157  
   158  	return strings.ToLower(strings.TrimSpace(str)), nil
   159  }