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