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