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