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