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 parseQueryBool(name string, dflt *bool, c querier) (*bool, error) { //nolint:SA4009 res may be not overwriten
58 str := c.Query(name)
59 if str == "" {
60 return dflt, nil
61 }
62 if str == "true" {
63 res := new(bool)
64 *res = true
65 return res, nil
66 }
67 if str == "false" {
68 res := new(bool)
69 *res = false
70 return res, nil
71 }
72 return nil, fmt.Errorf("Inavlid %s. Must be eithe true or false", name)
73}
74
75func parseQueryHezEthAddr(c querier) (*ethCommon.Address, error) {
76 const name = "hermezEthereumAddress"
77 addrStr := c.Query(name)
78 return hezStringToEthAddr(addrStr, name)
79}
80
81func parseQueryBJJ(c querier) (*babyjub.PublicKey, error) {
82 const name = "BJJ"
83 bjjStr := c.Query(name)
84 if bjjStr == "" {
85 return nil, nil
86 }
87 return hezStringToBJJ(bjjStr, name)
88}
89
90func parseQueryTxType(c querier) (*common.TxType, error) {
91 const name = "type"
92 typeStr := c.Query(name)
93 if typeStr == "" {
94 return nil, nil
95 }
96 switch common.TxType(typeStr) {
97 case common.TxTypeExit:
98 ret := common.TxTypeExit
99 return &ret, nil
100 case common.TxTypeTransfer:
101 ret := common.TxTypeTransfer
102 return &ret, nil
103 case common.TxTypeDeposit:
104 ret := common.TxTypeDeposit
105 return &ret, nil
106 case common.TxTypeCreateAccountDeposit:
107 ret := common.TxTypeCreateAccountDeposit
108 return &ret, nil
109 case common.TxTypeCreateAccountDepositTransfer:
110 ret := common.TxTypeCreateAccountDepositTransfer
111 return &ret, nil
112 case common.TxTypeDepositTransfer:
113 ret := common.TxTypeDepositTransfer
114 return &ret, nil
115 case common.TxTypeForceTransfer:
116 ret := common.TxTypeForceTransfer
117 return &ret, nil
118 case common.TxTypeForceExit:
119 ret := common.TxTypeForceExit
120 return &ret, nil
121 case common.TxTypeTransferToEthAddr:
122 ret := common.TxTypeTransferToEthAddr
123 return &ret, nil
124 case common.TxTypeTransferToBJJ:
125 ret := common.TxTypeTransferToBJJ
126 return &ret, nil
127 }
128 return nil, fmt.Errorf(
129 "invalid %s, %s is not a valid option. Check the valid options in the docmentation",
130 name, typeStr,
131 )
132}
133
134func parseIdx(c querier) (*common.Idx, error) {
135 const name = "accountIndex"
136 idxStr := c.Query(name)
137 return stringToIdx(idxStr, name)
138}
139
140func parseAccountFilters(c querier) (*common.TokenID, *ethCommon.Address, *babyjub.PublicKey, *common.Idx, error) {
141 // TokenID
142 tid, err := parseQueryUint("tokenId", nil, 0, maxUint32, c)
143 if err != nil {
144 return nil, nil, nil, nil, err
145 }
146 var tokenID *common.TokenID
147 if tid != nil {
148 tokenID = new(common.TokenID)
149 *tokenID = common.TokenID(*tid)
150 }
151 // Hez Eth addr
152 addr, err := parseQueryHezEthAddr(c)
153 if err != nil {
154 return nil, nil, nil, nil, err
155 }
156 // BJJ
157 bjj, err := parseQueryBJJ(c)
158 if err != nil {
159 return nil, nil, nil, nil, err
160 }
161 if addr != nil && bjj != nil {
162 return nil, nil, nil, nil,
163 errors.New("bjj and hermezEthereumAddress params are incompatible")
164 }
165 // Idx
166 idx, err := parseIdx(c)
167 if err != nil {
168 return nil, nil, nil, nil, err
169 }
170 if idx != nil && (addr != nil || bjj != nil || tokenID != nil) {
171 return nil, nil, nil, nil,
172 errors.New("accountIndex is incompatible with BJJ, hermezEthereumAddress and tokenId")
173 }
174 return tokenID, addr, bjj, idx, nil
175}
176
177func parseTokenFilters(c querier) ([]common.TokenID, []string, string, error) {
178 idsStr := c.Query("ids")
179 symbolsStr := c.Query("symbols")
180 nameStr := c.Query("name")
181 var tokensIDs []common.TokenID
182 if idsStr != "" {
183 ids := strings.Split(idsStr, ",")
184
185 for _, id := range ids {
186 idUint, err := strconv.Atoi(id)
187 if err != nil {
188 return nil, nil, "", err
189 }
190 tokenID := common.TokenID(idUint)
191 tokensIDs = append(tokensIDs, tokenID)
192 }
193 }
194 var symbols []string
195 if symbolsStr != "" {
196 symbols = strings.Split(symbolsStr, ",")
197 }
198 return tokensIDs, symbols, nameStr, nil
199}
200
201func parseBidFilters(c querier) (*uint, *ethCommon.Address, error) {
202 slotNum, err := parseQueryUint("slotNum", nil, 0, maxUint32, c)
203 if err != nil {
204 return nil, nil, err
205 }
206 bidderAddr, err := parseQueryEthAddr("bidderAddr", c)
207 if err != nil {
208 return nil, nil, err
209 }
210 return slotNum, bidderAddr, nil
211}
212
213// Param parsers
214
215type paramer interface {
216 Param(string) string
217}
218
219func parseParamTxID(c paramer) (common.TxID, error) {
220 const name = "id"
221 txIDStr := c.Param(name)
222 if txIDStr == "" {
223 return common.TxID{}, fmt.Errorf("%s is required", name)
224 }
225 txID, err := common.NewTxIDFromString(txIDStr)
226 if err != nil {
227 return common.TxID{}, fmt.Errorf("invalid %s", name)
228 }
229 return txID, nil
230}
231
232func parseParamIdx(c paramer) (*common.Idx, error) {
233 const name = "accountIndex"
234 idxStr := c.Param(name)
235 return stringToIdx(idxStr, name)
236}
237
238func parseParamUint(name string, dflt *uint, min, max uint, c paramer) (*uint, error) { //nolint:SA4009 res may be not overwriten
239 str := c.Param(name)
240 return stringToUint(str, name, dflt, min, max)
241}
242
243func stringToIdx(idxStr, name string) (*common.Idx, error) {
244 if idxStr == "" {
245 return nil, nil
246 }
247 splitted := strings.Split(idxStr, ":")
248 const expectedLen = 3
249 if len(splitted) != expectedLen || splitted[0] != "hez" {
250 return nil, fmt.Errorf(
251 "invalid %s, must follow this: hez:<tokenSymbol>:index", name)
252 }
253 // TODO: check that the tokenSymbol match the token related to the account index
254 idxInt, err := strconv.Atoi(splitted[2])
255 idx := common.Idx(idxInt)
256 return &idx, err
257}
258
259func stringToUint(uintStr, name string, dflt *uint, min, max uint) (*uint, error) {
260 if uintStr != "" {
261 resInt, err := strconv.Atoi(uintStr)
262 if err != nil || resInt < 0 || resInt < int(min) || resInt > int(max) {
263 return nil, fmt.Errorf(
264 "Inavlid %s. Must be an integer within the range [%d, %d]",
265 name, min, max)
266 }
267 res := uint(resInt)
268 return &res, nil
269 }
270 return dflt, nil
271}
272
273func hezStringToEthAddr(addrStr, name string) (*ethCommon.Address, error) {
274 if addrStr == "" {
275 return nil, nil
276 }
277 splitted := strings.Split(addrStr, "hez:")
278 if len(splitted) != 2 || len(splitted[1]) != 42 {
279 return nil, fmt.Errorf(
280 "Invalid %s, must follow this regex: ^hez:0x[a-fA-F0-9]{40}$", name)
281 }
282 var addr ethCommon.Address
283 err := addr.UnmarshalText([]byte(splitted[1]))
284 return &addr, err
285}
286
287func hezStringToBJJ(bjjStr, name string) (*babyjub.PublicKey, error) {
288 const decodedLen = 33
289 splitted := strings.Split(bjjStr, "hez:")
290 if len(splitted) != 2 || len(splitted[1]) != 44 {
291 return nil, fmt.Errorf(
292 "Invalid %s, must follow this regex: ^hez:[A-Za-z0-9+/=]{44}$",
293 name)
294 }
295 decoded, err := base64.RawURLEncoding.DecodeString(splitted[1])
296 if err != nil {
297 return nil, fmt.Errorf(
298 "Invalid %s, error decoding base64 string: %s",
299 name, err.Error())
300 }
301 if len(decoded) != decodedLen {
302 return nil, fmt.Errorf(
303 "invalid %s, error decoding base64 string: unexpected byte array length",
304 name)
305 }
306 bjjBytes := [decodedLen - 1]byte{}
307 copy(bjjBytes[:decodedLen-1], decoded[:decodedLen-1])
308 sum := bjjBytes[0]
309 for i := 1; i < len(bjjBytes); i++ {
310 sum += bjjBytes[i]
311 }
312 if decoded[decodedLen-1] != sum {
313 return nil, fmt.Errorf("invalid %s, checksum failed",
314 name)
315 }
316 bjjComp := babyjub.PublicKeyComp(bjjBytes)
317 bjj, err := bjjComp.Decompress()
318 if err != nil {
319 return nil, fmt.Errorf(
320 "invalid %s, error decompressing public key: %s",
321 name, err.Error())
322 }
323 return bjj, nil
324}
325
326func parseQueryEthAddr(name string, c querier) (*ethCommon.Address, error) {
327 addrStr := c.Query(name)
328 if addrStr == "" {
329 return nil, nil
330 }
331 return parseEthAddr(addrStr)
332}
333
334func parseParamEthAddr(name string, c paramer) (*ethCommon.Address, error) {
335 addrStr := c.Param(name)
336 if addrStr == "" {
337 return nil, nil
338 }
339 return parseEthAddr(addrStr)
340}
341
342func parseEthAddr(ethAddrStr string) (*ethCommon.Address, error) {
343 var addr ethCommon.Address
344 err := addr.UnmarshalText([]byte(ethAddrStr))
345 return &addr, err
346}
347
348func parseParamHezEthAddr(c paramer) (*ethCommon.Address, error) {
349 const name = "hermezEthereumAddress"
350 addrStr := c.Param(name)
351 return hezStringToEthAddr(addrStr, name)
352}