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