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