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
222// Param parsers
223
224type paramer interface {
225 Param(string) string
226}
227
228func parseParamTxID(c paramer) (common.TxID, error) {
229 const name = "id"
230 txIDStr := c.Param(name)
231 if txIDStr == "" {
232 return common.TxID{}, fmt.Errorf("%s is required", name)
233 }
234 txID, err := common.NewTxIDFromString(txIDStr)
235 if err != nil {
236 return common.TxID{}, fmt.Errorf("invalid %s", name)
237 }
238 return txID, nil
239}
240
241func parseParamIdx(c paramer) (*common.Idx, error) {
242 const name = "accountIndex"
243 idxStr := c.Param(name)
244 return stringToIdx(idxStr, name)
245}
246
247func parseParamUint(name string, dflt *uint, min, max uint, c paramer) (*uint, error) { //nolint:SA4009 res may be not overwriten
248 str := c.Param(name)
249 return stringToUint(str, name, dflt, min, max)
250}
251
252func stringToIdx(idxStr, name string) (*common.Idx, error) {
253 if idxStr == "" {
254 return nil, nil
255 }
256 splitted := strings.Split(idxStr, ":")
257 const expectedLen = 3
258 if len(splitted) != expectedLen || splitted[0] != "hez" {
259 return nil, fmt.Errorf(
260 "invalid %s, must follow this: hez:<tokenSymbol>:index", name)
261 }
262 // TODO: check that the tokenSymbol match the token related to the account index
263 idxInt, err := strconv.Atoi(splitted[2])
264 idx := common.Idx(idxInt)
265 return &idx, err
266}
267
268func stringToUint(uintStr, name string, dflt *uint, min, max uint) (*uint, error) {
269 if uintStr != "" {
270 resInt, err := strconv.Atoi(uintStr)
271 if err != nil || resInt < 0 || resInt < int(min) || resInt > int(max) {
272 return nil, fmt.Errorf(
273 "Inavlid %s. Must be an integer within the range [%d, %d]",
274 name, min, max)
275 }
276 res := uint(resInt)
277 return &res, nil
278 }
279 return dflt, nil
280}