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 parseAccountFilters(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
238// Param parsers
239
240type paramer interface {
241 Param(string) string
242}
243
244func parseParamTxID(c paramer) (common.TxID, error) {
245 const name = "id"
246 txIDStr := c.Param(name)
247 if txIDStr == "" {
248 return common.TxID{}, fmt.Errorf("%s is required", name)
249 }
250 txID, err := common.NewTxIDFromString(txIDStr)
251 if err != nil {
252 return common.TxID{}, fmt.Errorf("invalid %s", name)
253 }
254 return txID, nil
255}
256
257func parseParamIdx(c paramer) (*common.Idx, error) {
258 const name = "accountIndex"
259 idxStr := c.Param(name)
260 return stringToIdx(idxStr, name)
261}
262
263func parseParamUint(name string, dflt *uint, min, max uint, c paramer) (*uint, error) { //nolint:SA4009 res may be not overwriten
264 str := c.Param(name)
265 return stringToUint(str, name, dflt, min, max)
266}
267
268func parseParamInt64(name string, dflt *int64, min, max int64, c paramer) (*int64, error) { //nolint:SA4009 res may be not overwriten
269 str := c.Param(name)
270 return stringToInt64(str, name, dflt, min, max)
271}
272
273func stringToIdx(idxStr, name string) (*common.Idx, error) {
274 if idxStr == "" {
275 return nil, nil
276 }
277 splitted := strings.Split(idxStr, ":")
278 const expectedLen = 3
279 if len(splitted) != expectedLen || splitted[0] != "hez" {
280 return nil, fmt.Errorf(
281 "invalid %s, must follow this: hez:<tokenSymbol>:index", name)
282 }
283 // TODO: check that the tokenSymbol match the token related to the account index
284 idxInt, err := strconv.Atoi(splitted[2])
285 idx := common.Idx(idxInt)
286 return &idx, err
287}
288
289func stringToUint(uintStr, name string, dflt *uint, min, max uint) (*uint, error) {
290 if uintStr != "" {
291 resInt, err := strconv.Atoi(uintStr)
292 if err != nil || resInt < 0 || resInt < int(min) || resInt > int(max) {
293 return nil, fmt.Errorf(
294 "Invalid %s. Must be an integer within the range [%d, %d]",
295 name, min, max)
296 }
297 res := uint(resInt)
298 return &res, nil
299 }
300 return dflt, nil
301}
302
303func stringToInt64(uintStr, name string, dflt *int64, min, max int64) (*int64, error) {
304 if uintStr != "" {
305 resInt, err := strconv.Atoi(uintStr)
306 if err != nil || resInt < 0 || resInt < int(min) || resInt > int(max) {
307 return nil, fmt.Errorf(
308 "Invalid %s. Must be an integer within the range [%d, %d]",
309 name, min, max)
310 }
311 res := int64(resInt)
312 return &res, nil
313 }
314 return dflt, nil
315}
316
317func hezStringToEthAddr(addrStr, name string) (*ethCommon.Address, error) {
318 if addrStr == "" {
319 return nil, nil
320 }
321 splitted := strings.Split(addrStr, "hez:")
322 if len(splitted) != 2 || len(splitted[1]) != 42 {
323 return nil, fmt.Errorf(
324 "Invalid %s, must follow this regex: ^hez:0x[a-fA-F0-9]{40}$", name)
325 }
326 var addr ethCommon.Address
327 err := addr.UnmarshalText([]byte(splitted[1]))
328 return &addr, err
329}
330
331func hezStringToBJJ(bjjStr, name string) (*babyjub.PublicKey, error) {
332 const decodedLen = 33
333 splitted := strings.Split(bjjStr, "hez:")
334 if len(splitted) != 2 || len(splitted[1]) != 44 {
335 return nil, fmt.Errorf(
336 "Invalid %s, must follow this regex: ^hez:[A-Za-z0-9+/=]{44}$",
337 name)
338 }
339 decoded, err := base64.RawURLEncoding.DecodeString(splitted[1])
340 if err != nil {
341 return nil, fmt.Errorf(
342 "Invalid %s, error decoding base64 string: %s",
343 name, err.Error())
344 }
345 if len(decoded) != decodedLen {
346 return nil, fmt.Errorf(
347 "invalid %s, error decoding base64 string: unexpected byte array length",
348 name)
349 }
350 bjjBytes := [decodedLen - 1]byte{}
351 copy(bjjBytes[:decodedLen-1], decoded[:decodedLen-1])
352 sum := bjjBytes[0]
353 for i := 1; i < len(bjjBytes); i++ {
354 sum += bjjBytes[i]
355 }
356 if decoded[decodedLen-1] != sum {
357 return nil, fmt.Errorf("invalid %s, checksum failed",
358 name)
359 }
360 bjjComp := babyjub.PublicKeyComp(bjjBytes)
361 bjj, err := bjjComp.Decompress()
362 if err != nil {
363 return nil, fmt.Errorf(
364 "invalid %s, error decompressing public key: %s",
365 name, err.Error())
366 }
367 return bjj, nil
368}
369
370func parseQueryEthAddr(name string, c querier) (*ethCommon.Address, error) {
371 addrStr := c.Query(name)
372 if addrStr == "" {
373 return nil, nil
374 }
375 return parseEthAddr(addrStr)
376}
377
378func parseParamEthAddr(name string, c paramer) (*ethCommon.Address, error) {
379 addrStr := c.Param(name)
380 if addrStr == "" {
381 return nil, nil
382 }
383 return parseEthAddr(addrStr)
384}
385
386func parseEthAddr(ethAddrStr string) (*ethCommon.Address, error) {
387 var addr ethCommon.Address
388 err := addr.UnmarshalText([]byte(ethAddrStr))
389 return &addr, err
390}
391
392func parseParamHezEthAddr(c paramer) (*ethCommon.Address, error) {
393 const name = "hermezEthereumAddress"
394 addrStr := c.Param(name)
395 return hezStringToEthAddr(addrStr, name)
396}