hermez-node/api/parsers.go
api/parsers.goRaw
1package api
2
3import (
4 "encoding/base64"
5 "errors"
6 "fmt"
7 "strconv"
8 "strings"
9
10 ethCommon "github.com/ethereum/go-ethereum/common"
11 "github.com/hermeznetwork/hermez-node/common"
12 "github.com/hermeznetwork/hermez-node/db/historydb"
13 "github.com/iden3/go-iden3-crypto/babyjub"
14)
15
16// Query parsers
17
18type querier interface {
19 Query(string) string
20}
21
22func parsePagination(c querier) (fromItem *uint, order string, limit *uint, err error) {
23 // FromItem
24 fromItem, err = parseQueryUint("fromItem", nil, 0, maxUint32, c)
25 if err != nil {
26 return nil, "", nil, err
27 }
28 // Order
29 order = dfltOrder
30 const orderName = "order"
31 orderStr := c.Query(orderName)
32 if orderStr != "" && !(orderStr == historydb.OrderAsc || historydb.OrderDesc == orderStr) {
33 return nil, "", nil, errors.New(
34 "order must have the value " + historydb.OrderAsc + " or " + historydb.OrderDesc,
35 )
36 }
37 if orderStr == historydb.OrderAsc {
38 order = historydb.OrderAsc
39 } else if orderStr == historydb.OrderDesc {
40 order = historydb.OrderDesc
41 }
42 // Limit
43 limit = new(uint)
44 *limit = dfltLimit
45 limit, err = parseQueryUint("limit", limit, 1, maxLimit, c)
46 if err != nil {
47 return nil, "", nil, err
48 }
49 return fromItem, order, limit, nil
50}
51
52func parseQueryUint(name string, dflt *uint, min, max uint, c querier) (*uint, error) { //nolint:SA4009 res may be not overwriten
53 str := c.Query(name)
54 return stringToUint(str, name, dflt, min, max)
55}
56
57func parseQueryInt64(name string, dflt *int64, min, max int64, c querier) (*int64, error) { //nolint:SA4009 res may be not overwriten
58 str := c.Query(name)
59 return stringToInt64(str, name, dflt, min, max)
60}
61
62func parseQueryBool(name string, dflt *bool, c querier) (*bool, error) { //nolint:SA4009 res may be not overwriten
63 str := c.Query(name)
64 if str == "" {
65 return dflt, nil
66 }
67 if str == "true" {
68 res := new(bool)
69 *res = true
70 return res, nil
71 }
72 if str == "false" {
73 res := new(bool)
74 *res = false
75 return res, nil
76 }
77 return nil, fmt.Errorf("Invalid %s. Must be eithe true or false", name)
78}
79
80func parseQueryHezEthAddr(c querier) (*ethCommon.Address, error) {
81 const name = "hermezEthereumAddress"
82 addrStr := c.Query(name)
83 return hezStringToEthAddr(addrStr, name)
84}
85
86func parseQueryBJJ(c querier) (*babyjub.PublicKey, error) {
87 const name = "BJJ"
88 bjjStr := c.Query(name)
89 if bjjStr == "" {
90 return nil, nil
91 }
92 return hezStringToBJJ(bjjStr, name)
93}
94
95func parseQueryTxType(c querier) (*common.TxType, error) {
96 const name = "type"
97 typeStr := c.Query(name)
98 if typeStr == "" {
99 return nil, nil
100 }
101 switch common.TxType(typeStr) {
102 case common.TxTypeExit:
103 ret := common.TxTypeExit
104 return &ret, nil
105 case common.TxTypeTransfer:
106 ret := common.TxTypeTransfer
107 return &ret, nil
108 case common.TxTypeDeposit:
109 ret := common.TxTypeDeposit
110 return &ret, nil
111 case common.TxTypeCreateAccountDeposit:
112 ret := common.TxTypeCreateAccountDeposit
113 return &ret, nil
114 case common.TxTypeCreateAccountDepositTransfer:
115 ret := common.TxTypeCreateAccountDepositTransfer
116 return &ret, nil
117 case common.TxTypeDepositTransfer:
118 ret := common.TxTypeDepositTransfer
119 return &ret, nil
120 case common.TxTypeForceTransfer:
121 ret := common.TxTypeForceTransfer
122 return &ret, nil
123 case common.TxTypeForceExit:
124 ret := common.TxTypeForceExit
125 return &ret, nil
126 case common.TxTypeTransferToEthAddr:
127 ret := common.TxTypeTransferToEthAddr
128 return &ret, nil
129 case common.TxTypeTransferToBJJ:
130 ret := common.TxTypeTransferToBJJ
131 return &ret, nil
132 }
133 return nil, fmt.Errorf(
134 "invalid %s, %s is not a valid option. Check the valid options in the docmentation",
135 name, typeStr,
136 )
137}
138
139func parseIdx(c querier) (*common.Idx, error) {
140 const name = "accountIndex"
141 idxStr := c.Query(name)
142 return stringToIdx(idxStr, name)
143}
144
145func parseExitFilters(c querier) (*common.TokenID, *ethCommon.Address, *babyjub.PublicKey, *common.Idx, error) {
146 // TokenID
147 tid, err := parseQueryUint("tokenId", nil, 0, maxUint32, c)
148 if err != nil {
149 return nil, nil, nil, nil, err
150 }
151 var tokenID *common.TokenID
152 if tid != nil {
153 tokenID = new(common.TokenID)
154 *tokenID = common.TokenID(*tid)
155 }
156 // Hez Eth addr
157 addr, err := parseQueryHezEthAddr(c)
158 if err != nil {
159 return nil, nil, nil, nil, err
160 }
161 // BJJ
162 bjj, err := parseQueryBJJ(c)
163 if err != nil {
164 return nil, nil, nil, nil, err
165 }
166 if addr != nil && bjj != nil {
167 return nil, nil, nil, nil,
168 errors.New("bjj and hermezEthereumAddress params are incompatible")
169 }
170 // Idx
171 idx, err := parseIdx(c)
172 if err != nil {
173 return nil, nil, nil, nil, err
174 }
175 if idx != nil && (addr != nil || bjj != nil || tokenID != nil) {
176 return nil, nil, nil, nil,
177 errors.New("accountIndex is incompatible with BJJ, hermezEthereumAddress and tokenId")
178 }
179 return tokenID, addr, bjj, idx, nil
180}
181
182func parseTokenFilters(c querier) ([]common.TokenID, []string, string, error) {
183 idsStr := c.Query("ids")
184 symbolsStr := c.Query("symbols")
185 nameStr := c.Query("name")
186 var tokensIDs []common.TokenID
187 if idsStr != "" {
188 ids := strings.Split(idsStr, ",")
189
190 for _, id := range ids {
191 idUint, err := strconv.Atoi(id)
192 if err != nil {
193 return nil, nil, "", err
194 }
195 tokenID := common.TokenID(idUint)
196 tokensIDs = append(tokensIDs, tokenID)
197 }
198 }
199 var symbols []string
200 if symbolsStr != "" {
201 symbols = strings.Split(symbolsStr, ",")
202 }
203 return tokensIDs, symbols, nameStr, nil
204}
205
206func parseBidFilters(c querier) (*int64, *ethCommon.Address, error) {
207 slotNum, err := parseQueryInt64("slotNum", nil, 0, maxInt64, c)
208 if err != nil {
209 return nil, nil, err
210 }
211 bidderAddr, err := parseQueryEthAddr("bidderAddr", c)
212 if err != nil {
213 return nil, nil, err
214 }
215 return slotNum, bidderAddr, nil
216}
217
218func parseSlotFilters(c querier) (*int64, *int64, *ethCommon.Address, *bool, error) {
219 minSlotNum, err := parseQueryInt64("minSlotNum", nil, 0, maxInt64, c)
220 if err != nil {
221 return nil, nil, nil, nil, err
222 }
223 maxSlotNum, err := parseQueryInt64("maxSlotNum", nil, 0, maxInt64, c)
224 if err != nil {
225 return nil, nil, nil, nil, err
226 }
227 wonByEthereumAddress, err := parseQueryEthAddr("wonByEthereumAddress", c)
228 if err != nil {
229 return nil, nil, nil, nil, err
230 }
231 finishedAuction, err := parseQueryBool("finishedAuction", nil, c)
232 if err != nil {
233 return nil, nil, nil, nil, err
234 }
235 return minSlotNum, maxSlotNum, wonByEthereumAddress, finishedAuction, nil
236}
237
238func parseAccountFilters(c querier) ([]common.TokenID, *ethCommon.Address, *babyjub.PublicKey, error) {
239 // TokenID
240 idsStr := c.Query("tokenIds")
241 var tokenIDs []common.TokenID
242 if idsStr != "" {
243 ids := strings.Split(idsStr, ",")
244
245 for _, id := range ids {
246 idUint, err := strconv.Atoi(id)
247 if err != nil {
248 return nil, nil, nil, err
249 }
250 tokenID := common.TokenID(idUint)
251 tokenIDs = append(tokenIDs, tokenID)
252 }
253 }
254 // Hez Eth addr
255 addr, err := parseQueryHezEthAddr(c)
256 if err != nil {
257 return nil, nil, nil, err
258 }
259 // BJJ
260 bjj, err := parseQueryBJJ(c)
261 if err != nil {
262 return nil, nil, nil, err
263 }
264 if addr != nil && bjj != nil {
265 return nil, nil, nil,
266 errors.New("bjj and hermezEthereumAddress params are incompatible")
267 }
268
269 return tokenIDs, addr, bjj, nil
270}
271
272// Param parsers
273
274type paramer interface {
275 Param(string) string
276}
277
278func parseParamTxID(c paramer) (common.TxID, error) {
279 const name = "id"
280 txIDStr := c.Param(name)
281 if txIDStr == "" {
282 return common.TxID{}, fmt.Errorf("%s is required", name)
283 }
284 txID, err := common.NewTxIDFromString(txIDStr)
285 if err != nil {
286 return common.TxID{}, fmt.Errorf("invalid %s", name)
287 }
288 return txID, nil
289}
290
291func parseParamIdx(c paramer) (*common.Idx, error) {
292 const name = "accountIndex"
293 idxStr := c.Param(name)
294 return stringToIdx(idxStr, name)
295}
296
297func parseParamUint(name string, dflt *uint, min, max uint, c paramer) (*uint, error) { //nolint:SA4009 res may be not overwriten
298 str := c.Param(name)
299 return stringToUint(str, name, dflt, min, max)
300}
301
302func parseParamInt64(name string, dflt *int64, min, max int64, c paramer) (*int64, error) { //nolint:SA4009 res may be not overwriten
303 str := c.Param(name)
304 return stringToInt64(str, name, dflt, min, max)
305}
306
307func stringToIdx(idxStr, name string) (*common.Idx, error) {
308 if idxStr == "" {
309 return nil, nil
310 }
311 splitted := strings.Split(idxStr, ":")
312 const expectedLen = 3
313 if len(splitted) != expectedLen || splitted[0] != "hez" {
314 return nil, fmt.Errorf(
315 "invalid %s, must follow this: hez:<tokenSymbol>:index", name)
316 }
317 // TODO: check that the tokenSymbol match the token related to the account index
318 idxInt, err := strconv.Atoi(splitted[2])
319 idx := common.Idx(idxInt)
320 return &idx, err
321}
322
323func stringToUint(uintStr, name string, dflt *uint, min, max uint) (*uint, error) {
324 if uintStr != "" {
325 resInt, err := strconv.Atoi(uintStr)
326 if err != nil || resInt < 0 || resInt < int(min) || resInt > int(max) {
327 return nil, fmt.Errorf(
328 "Invalid %s. Must be an integer within the range [%d, %d]",
329 name, min, max)
330 }
331 res := uint(resInt)
332 return &res, nil
333 }
334 return dflt, nil
335}
336
337func stringToInt64(uintStr, name string, dflt *int64, min, max int64) (*int64, error) {
338 if uintStr != "" {
339 resInt, err := strconv.Atoi(uintStr)
340 if err != nil || resInt < 0 || resInt < int(min) || resInt > int(max) {
341 return nil, fmt.Errorf(
342 "Invalid %s. Must be an integer within the range [%d, %d]",
343 name, min, max)
344 }
345 res := int64(resInt)
346 return &res, nil
347 }
348 return dflt, nil
349}
350
351func hezStringToEthAddr(addrStr, name string) (*ethCommon.Address, error) {
352 if addrStr == "" {
353 return nil, nil
354 }
355 splitted := strings.Split(addrStr, "hez:")
356 if len(splitted) != 2 || len(splitted[1]) != 42 {
357 return nil, fmt.Errorf(
358 "Invalid %s, must follow this regex: ^hez:0x[a-fA-F0-9]{40}$", name)
359 }
360 var addr ethCommon.Address
361 err := addr.UnmarshalText([]byte(splitted[1]))
362 return &addr, err
363}
364
365func hezStringToBJJ(bjjStr, name string) (*babyjub.PublicKey, error) {
366 const decodedLen = 33
367 splitted := strings.Split(bjjStr, "hez:")
368 if len(splitted) != 2 || len(splitted[1]) != 44 {
369 return nil, fmt.Errorf(
370 "Invalid %s, must follow this regex: ^hez:[A-Za-z0-9+/=]{44}$",
371 name)
372 }
373 decoded, err := base64.RawURLEncoding.DecodeString(splitted[1])
374 if err != nil {
375 return nil, fmt.Errorf(
376 "Invalid %s, error decoding base64 string: %s",
377 name, err.Error())
378 }
379 if len(decoded) != decodedLen {
380 return nil, fmt.Errorf(
381 "invalid %s, error decoding base64 string: unexpected byte array length",
382 name)
383 }
384 bjjBytes := [decodedLen - 1]byte{}
385 copy(bjjBytes[:decodedLen-1], decoded[:decodedLen-1])
386 sum := bjjBytes[0]
387 for i := 1; i < len(bjjBytes); i++ {
388 sum += bjjBytes[i]
389 }
390 if decoded[decodedLen-1] != sum {
391 return nil, fmt.Errorf("invalid %s, checksum failed",
392 name)
393 }
394 bjjComp := babyjub.PublicKeyComp(bjjBytes)
395 bjj, err := bjjComp.Decompress()
396 if err != nil {
397 return nil, fmt.Errorf(
398 "invalid %s, error decompressing public key: %s",
399 name, err.Error())
400 }
401 return bjj, nil
402}
403
404func parseQueryEthAddr(name string, c querier) (*ethCommon.Address, error) {
405 addrStr := c.Query(name)
406 if addrStr == "" {
407 return nil, nil
408 }
409 return parseEthAddr(addrStr)
410}
411
412func parseParamEthAddr(name string, c paramer) (*ethCommon.Address, error) {
413 addrStr := c.Param(name)
414 if addrStr == "" {
415 return nil, nil
416 }
417 return parseEthAddr(addrStr)
418}
419
420func parseEthAddr(ethAddrStr string) (*ethCommon.Address, error) {
421 var addr ethCommon.Address
422 err := addr.UnmarshalText([]byte(ethAddrStr))
423 return &addr, err
424}
425
426func parseParamHezEthAddr(c paramer) (*ethCommon.Address, error) {
427 const name = "hermezEthereumAddress"
428 addrStr := c.Param(name)
429 return hezStringToEthAddr(addrStr, name)
430}