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 }