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
201// Param parsers
202
203type paramer interface {
204 Param(string) string
205}
206
207func parseParamTxID(c paramer) (common.TxID, error) {
208 const name = "id"
209 txIDStr := c.Param(name)
210 if txIDStr == "" {
211 return common.TxID{}, fmt.Errorf("%s is required", name)
212 }
213 txID, err := common.NewTxIDFromString(txIDStr)
214 if err != nil {
215 return common.TxID{}, fmt.Errorf("invalid %s", name)
216 }
217 return txID, nil
218}
219
220func parseParamIdx(c paramer) (*common.Idx, error) {
221 const name = "accountIndex"
222 idxStr := c.Param(name)
223 return stringToIdx(idxStr, name)
224}
225
226func parseParamUint(name string, dflt *uint, min, max uint, c paramer) (*uint, error) { //nolint:SA4009 res may be not overwriten
227 str := c.Param(name)
228 return stringToUint(str, name, dflt, min, max)
229}
230
231func stringToIdx(idxStr, name string) (*common.Idx, error) {
232 if idxStr == "" {
233 return nil, nil
234 }
235 splitted := strings.Split(idxStr, ":")
236 const expectedLen = 3
237 if len(splitted) != expectedLen || splitted[0] != "hez" {
238 return nil, fmt.Errorf(
239 "invalid %s, must follow this: hez:<tokenSymbol>:index", name)
240 }
241 // TODO: check that the tokenSymbol match the token related to the account index
242 idxInt, err := strconv.Atoi(splitted[2])
243 idx := common.Idx(idxInt)
244 return &idx, err
245}
246
247func stringToUint(uintStr, name string, dflt *uint, min, max uint) (*uint, error) {
248 if uintStr != "" {
249 resInt, err := strconv.Atoi(uintStr)
250 if err != nil || resInt < 0 || resInt < int(min) || resInt > int(max) {
251 return nil, fmt.Errorf(
252 "Inavlid %s. Must be an integer within the range [%d, %d]",
253 name, min, max)
254 }
255 res := uint(resInt)
256 return &res, nil
257 }
258 return dflt, nil
259}
260
261func hezStringToEthAddr(addrStr, name string) (*ethCommon.Address, error) {
262 if addrStr == "" {
263 return nil, nil
264 }
265 splitted := strings.Split(addrStr, "hez:")
266 if len(splitted) != 2 || len(splitted[1]) != 42 {
267 return nil, fmt.Errorf(
268 "Invalid %s, must follow this regex: ^hez:0x[a-fA-F0-9]{40}$", name)
269 }
270 var addr ethCommon.Address
271 err := addr.UnmarshalText([]byte(splitted[1]))
272 return &addr, err
273}
274
275func hezStringToBJJ(bjjStr, name string) (*babyjub.PublicKey, error) {
276 const decodedLen = 33
277 splitted := strings.Split(bjjStr, "hez:")
278 if len(splitted) != 2 || len(splitted[1]) != 44 {
279 return nil, fmt.Errorf(
280 "Invalid %s, must follow this regex: ^hez:[A-Za-z0-9+/=]{44}$",
281 name)
282 }
283 decoded, err := base64.RawURLEncoding.DecodeString(splitted[1])
284 if err != nil {
285 return nil, fmt.Errorf(
286 "Invalid %s, error decoding base64 string: %s",
287 name, err.Error())
288 }
289 if len(decoded) != decodedLen {
290 return nil, fmt.Errorf(
291 "invalid %s, error decoding base64 string: unexpected byte array length",
292 name)
293 }
294 bjjBytes := [decodedLen - 1]byte{}
295 copy(bjjBytes[:decodedLen-1], decoded[:decodedLen-1])
296 sum := bjjBytes[0]
297 for i := 1; i < len(bjjBytes); i++ {
298 sum += bjjBytes[i]
299 }
300 if decoded[decodedLen-1] != sum {
301 return nil, fmt.Errorf("invalid %s, checksum failed",
302 name)
303 }
304 bjjComp := babyjub.PublicKeyComp(bjjBytes)
305 bjj, err := bjjComp.Decompress()
306 if err != nil {
307 return nil, fmt.Errorf(
308 "invalid %s, error decompressing public key: %s",
309 name, err.Error())
310 }
311 return bjj, nil
312}
313
314func parseQueryEthAddr(name string, c querier) (*ethCommon.Address, error) {
315 addrStr := c.Query(name)
316 if addrStr == "" {
317 return nil, nil
318 }
319 return parseEthAddr(addrStr)
320}
321
322func parseParamEthAddr(name string, c paramer) (*ethCommon.Address, error) {
323 addrStr := c.Param(name)
324 if addrStr == "" {
325 return nil, nil
326 }
327 return parseEthAddr(addrStr)
328}
329
330func parseEthAddr(ethAddrStr string) (*ethCommon.Address, error) {
331 var addr ethCommon.Address
332 err := addr.UnmarshalText([]byte(ethAddrStr))
333 return &addr, err
334}
335
336func parseParamHezEthAddr(c paramer) (*ethCommon.Address, error) {
337 const name = "hermezEthereumAddress"
338 addrStr := c.Param(name)
339 return hezStringToEthAddr(addrStr, name)
340}