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
 16const exitIdx = "hez:EXIT:1"
 17
 18// Query parsers
 19
 20type querier interface {
 21	Query(string) string
 22}
 23
 24func parsePagination(c querier) (fromItem *uint, order string, limit *uint, err error) {
 25	// FromItem
 26	fromItem, err = parseQueryUint("fromItem", nil, 0, maxUint32, c)
 27	if err != nil {
 28		return nil, "", nil, err
 29	}
 30	// Order
 31	order = dfltOrder
 32	const orderName = "order"
 33	orderStr := c.Query(orderName)
 34	if orderStr != "" && !(orderStr == historydb.OrderAsc || historydb.OrderDesc == orderStr) {
 35		return nil, "", nil, errors.New(
 36			"order must have the value " + historydb.OrderAsc + " or " + historydb.OrderDesc,
 37		)
 38	}
 39	if orderStr == historydb.OrderAsc {
 40		order = historydb.OrderAsc
 41	} else if orderStr == historydb.OrderDesc {
 42		order = historydb.OrderDesc
 43	}
 44	// Limit
 45	limit = new(uint)
 46	*limit = dfltLimit
 47	limit, err = parseQueryUint("limit", limit, 1, maxLimit, c)
 48	if err != nil {
 49		return nil, "", nil, err
 50	}
 51	return fromItem, order, limit, nil
 52}
 53
 54func parseQueryUint(name string, dflt *uint, min, max uint, c querier) (*uint, error) { //nolint:SA4009 res may be not overwriten
 55	str := c.Query(name)
 56	return stringToUint(str, name, dflt, min, max)
 57}
 58
 59func parseQueryInt64(name string, dflt *int64, min, max int64, c querier) (*int64, error) { //nolint:SA4009 res may be not overwriten
 60	str := c.Query(name)
 61	return stringToInt64(str, name, dflt, min, max)
 62}
 63
 64func parseQueryBool(name string, dflt *bool, c querier) (*bool, error) { //nolint:SA4009 res may be not overwriten
 65	str := c.Query(name)
 66	if str == "" {
 67		return dflt, nil
 68	}
 69	if str == "true" {
 70		res := new(bool)
 71		*res = true
 72		return res, nil
 73	}
 74	if str == "false" {
 75		res := new(bool)
 76		*res = false
 77		return res, nil
 78	}
 79	return nil, fmt.Errorf("Invalid %s. Must be eithe true or false", name)
 80}
 81
 82func parseQueryHezEthAddr(c querier) (*ethCommon.Address, error) {
 83	const name = "hermezEthereumAddress"
 84	addrStr := c.Query(name)
 85	return hezStringToEthAddr(addrStr, name)
 86}
 87
 88func parseQueryBJJ(c querier) (*babyjub.PublicKey, error) {
 89	const name = "BJJ"
 90	bjjStr := c.Query(name)
 91	if bjjStr == "" {
 92		return nil, nil
 93	}
 94	return hezStringToBJJ(bjjStr, name)
 95}
 96
 97func parseQueryTxType(c querier) (*common.TxType, error) {
 98	const name = "type"
 99	typeStr := c.Query(name)
100	if typeStr == "" {
101		return nil, nil
102	}
103	switch common.TxType(typeStr) {
104	case common.TxTypeExit:
105		ret := common.TxTypeExit
106		return &ret, nil
107	case common.TxTypeTransfer:
108		ret := common.TxTypeTransfer
109		return &ret, nil
110	case common.TxTypeDeposit:
111		ret := common.TxTypeDeposit
112		return &ret, nil
113	case common.TxTypeCreateAccountDeposit:
114		ret := common.TxTypeCreateAccountDeposit
115		return &ret, nil
116	case common.TxTypeCreateAccountDepositTransfer:
117		ret := common.TxTypeCreateAccountDepositTransfer
118		return &ret, nil
119	case common.TxTypeDepositTransfer:
120		ret := common.TxTypeDepositTransfer
121		return &ret, nil
122	case common.TxTypeForceTransfer:
123		ret := common.TxTypeForceTransfer
124		return &ret, nil
125	case common.TxTypeForceExit:
126		ret := common.TxTypeForceExit
127		return &ret, nil
128	case common.TxTypeTransferToEthAddr:
129		ret := common.TxTypeTransferToEthAddr
130		return &ret, nil
131	case common.TxTypeTransferToBJJ:
132		ret := common.TxTypeTransferToBJJ
133		return &ret, nil
134	}
135	return nil, fmt.Errorf(
136		"invalid %s, %s is not a valid option. Check the valid options in the docmentation",
137		name, typeStr,
138	)
139}
140
141func parseIdx(c querier) (*common.Idx, error) {
142	const name = "accountIndex"
143	idxStr := c.Query(name)
144	return stringToIdx(idxStr, name)
145}
146
147func parseExitFilters(c querier) (*common.TokenID, *ethCommon.Address, *babyjub.PublicKey, *common.Idx, error) {
148	// TokenID
149	tid, err := parseQueryUint("tokenId", nil, 0, maxUint32, c)
150	if err != nil {
151		return nil, nil, nil, nil, err
152	}
153	var tokenID *common.TokenID
154	if tid != nil {
155		tokenID = new(common.TokenID)
156		*tokenID = common.TokenID(*tid)
157	}
158	// Hez Eth addr
159	addr, err := parseQueryHezEthAddr(c)
160	if err != nil {
161		return nil, nil, nil, nil, err
162	}
163	// BJJ
164	bjj, err := parseQueryBJJ(c)
165	if err != nil {
166		return nil, nil, nil, nil, err
167	}
168	if addr != nil && bjj != nil {
169		return nil, nil, nil, nil,
170			errors.New("bjj and hermezEthereumAddress params are incompatible")
171	}
172	// Idx
173	idx, err := parseIdx(c)
174	if err != nil {
175		return nil, nil, nil, nil, err
176	}
177	if idx != nil && (addr != nil || bjj != nil || tokenID != nil) {
178		return nil, nil, nil, nil,
179			errors.New("accountIndex is incompatible with BJJ, hermezEthereumAddress and tokenId")
180	}
181	return tokenID, addr, bjj, idx, nil
182}
183
184func parseTokenFilters(c querier) ([]common.TokenID, []string, string, error) {
185	idsStr := c.Query("ids")
186	symbolsStr := c.Query("symbols")
187	nameStr := c.Query("name")
188	var tokensIDs []common.TokenID
189	if idsStr != "" {
190		ids := strings.Split(idsStr, ",")
191
192		for _, id := range ids {
193			idUint, err := strconv.Atoi(id)
194			if err != nil {
195				return nil, nil, "", err
196			}
197			tokenID := common.TokenID(idUint)
198			tokensIDs = append(tokensIDs, tokenID)
199		}
200	}
201	var symbols []string
202	if symbolsStr != "" {
203		symbols = strings.Split(symbolsStr, ",")
204	}
205	return tokensIDs, symbols, nameStr, nil
206}
207
208func parseBidFilters(c querier) (*int64, *ethCommon.Address, error) {
209	slotNum, err := parseQueryInt64("slotNum", nil, 0, maxInt64, c)
210	if err != nil {
211		return nil, nil, err
212	}
213	bidderAddr, err := parseQueryEthAddr("bidderAddr", c)
214	if err != nil {
215		return nil, nil, err
216	}
217	return slotNum, bidderAddr, nil
218}
219
220func parseSlotFilters(c querier) (*int64, *int64, *ethCommon.Address, *bool, error) {
221	minSlotNum, err := parseQueryInt64("minSlotNum", nil, 0, maxInt64, c)
222	if err != nil {
223		return nil, nil, nil, nil, err
224	}
225	maxSlotNum, err := parseQueryInt64("maxSlotNum", nil, 0, maxInt64, c)
226	if err != nil {
227		return nil, nil, nil, nil, err
228	}
229	wonByEthereumAddress, err := parseQueryEthAddr("wonByEthereumAddress", c)
230	if err != nil {
231		return nil, nil, nil, nil, err
232	}
233	finishedAuction, err := parseQueryBool("finishedAuction", nil, c)
234	if err != nil {
235		return nil, nil, nil, nil, err
236	}
237	return minSlotNum, maxSlotNum, wonByEthereumAddress, finishedAuction, nil
238}
239
240func parseAccountFilters(c querier) ([]common.TokenID, *ethCommon.Address, *babyjub.PublicKey, error) {
241	// TokenID
242	idsStr := c.Query("tokenIds")
243	var tokenIDs []common.TokenID
244	if idsStr != "" {
245		ids := strings.Split(idsStr, ",")
246
247		for _, id := range ids {
248			idUint, err := strconv.Atoi(id)
249			if err != nil {
250				return nil, nil, nil, err
251			}
252			tokenID := common.TokenID(idUint)
253			tokenIDs = append(tokenIDs, tokenID)
254		}
255	}
256	// Hez Eth addr
257	addr, err := parseQueryHezEthAddr(c)
258	if err != nil {
259		return nil, nil, nil, err
260	}
261	// BJJ
262	bjj, err := parseQueryBJJ(c)
263	if err != nil {
264		return nil, nil, nil, err
265	}
266	if addr != nil && bjj != nil {
267		return nil, nil, nil,
268			errors.New("bjj and hermezEthereumAddress params are incompatible")
269	}
270
271	return tokenIDs, addr, bjj, nil
272}
273
274// Param parsers
275
276type paramer interface {
277	Param(string) string
278}
279
280func parseParamTxID(c paramer) (common.TxID, error) {
281	const name = "id"
282	txIDStr := c.Param(name)
283	if txIDStr == "" {
284		return common.TxID{}, fmt.Errorf("%s is required", name)
285	}
286	txID, err := common.NewTxIDFromString(txIDStr)
287	if err != nil {
288		return common.TxID{}, fmt.Errorf("invalid %s", name)
289	}
290	return txID, nil
291}
292
293func parseParamIdx(c paramer) (*common.Idx, error) {
294	const name = "accountIndex"
295	idxStr := c.Param(name)
296	return stringToIdx(idxStr, name)
297}
298
299func parseParamUint(name string, dflt *uint, min, max uint, c paramer) (*uint, error) { //nolint:SA4009 res may be not overwriten
300	str := c.Param(name)
301	return stringToUint(str, name, dflt, min, max)
302}
303
304func parseParamInt64(name string, dflt *int64, min, max int64, c paramer) (*int64, error) { //nolint:SA4009 res may be not overwriten
305	str := c.Param(name)
306	return stringToInt64(str, name, dflt, min, max)
307}
308
309func stringToIdx(idxStr, name string) (*common.Idx, error) {
310	if idxStr == "" {
311		return nil, nil
312	}
313	splitted := strings.Split(idxStr, ":")
314	const expectedLen = 3
315	if len(splitted) != expectedLen || splitted[0] != "hez" {
316		return nil, fmt.Errorf(
317			"invalid %s, must follow this: hez:<tokenSymbol>:index", name)
318	}
319	// TODO: check that the tokenSymbol match the token related to the account index
320	idxInt, err := strconv.Atoi(splitted[2])
321	idx := common.Idx(idxInt)
322	return &idx, err
323}
324
325func stringToUint(uintStr, name string, dflt *uint, min, max uint) (*uint, error) {
326	if uintStr != "" {
327		resInt, err := strconv.Atoi(uintStr)
328		if err != nil || resInt < 0 || resInt < int(min) || resInt > int(max) {
329			return nil, fmt.Errorf(
330				"Invalid %s. Must be an integer within the range [%d, %d]",
331				name, min, max)
332		}
333		res := uint(resInt)
334		return &res, nil
335	}
336	return dflt, nil
337}
338
339func stringToInt64(uintStr, name string, dflt *int64, min, max int64) (*int64, error) {
340	if uintStr != "" {
341		resInt, err := strconv.Atoi(uintStr)
342		if err != nil || resInt < 0 || resInt < int(min) || resInt > int(max) {
343			return nil, fmt.Errorf(
344				"Invalid %s. Must be an integer within the range [%d, %d]",
345				name, min, max)
346		}
347		res := int64(resInt)
348		return &res, nil
349	}
350	return dflt, nil
351}
352
353func hezStringToEthAddr(addrStr, name string) (*ethCommon.Address, error) {
354	if addrStr == "" {
355		return nil, nil
356	}
357	splitted := strings.Split(addrStr, "hez:")
358	if len(splitted) != 2 || len(splitted[1]) != 42 {
359		return nil, fmt.Errorf(
360			"Invalid %s, must follow this regex: ^hez:0x[a-fA-F0-9]{40}$", name)
361	}
362	var addr ethCommon.Address
363	err := addr.UnmarshalText([]byte(splitted[1]))
364	return &addr, err
365}
366
367func hezStringToBJJ(bjjStr, name string) (*babyjub.PublicKey, error) {
368	const decodedLen = 33
369	splitted := strings.Split(bjjStr, "hez:")
370	if len(splitted) != 2 || len(splitted[1]) != 44 {
371		return nil, fmt.Errorf(
372			"Invalid %s, must follow this regex: ^hez:[A-Za-z0-9+/=]{44}$",
373			name)
374	}
375	decoded, err := base64.RawURLEncoding.DecodeString(splitted[1])
376	if err != nil {
377		return nil, fmt.Errorf(
378			"Invalid %s, error decoding base64 string: %s",
379			name, err.Error())
380	}
381	if len(decoded) != decodedLen {
382		return nil, fmt.Errorf(
383			"invalid %s, error decoding base64 string: unexpected byte array length",
384			name)
385	}
386	bjjBytes := [decodedLen - 1]byte{}
387	copy(bjjBytes[:decodedLen-1], decoded[:decodedLen-1])
388	sum := bjjBytes[0]
389	for i := 1; i < len(bjjBytes); i++ {
390		sum += bjjBytes[i]
391	}
392	if decoded[decodedLen-1] != sum {
393		return nil, fmt.Errorf("invalid %s, checksum failed",
394			name)
395	}
396	bjjComp := babyjub.PublicKeyComp(bjjBytes)
397	bjj, err := bjjComp.Decompress()
398	if err != nil {
399		return nil, fmt.Errorf(
400			"invalid %s, error decompressing public key: %s",
401			name, err.Error())
402	}
403	return bjj, nil
404}
405
406func parseQueryEthAddr(name string, c querier) (*ethCommon.Address, error) {
407	addrStr := c.Query(name)
408	if addrStr == "" {
409		return nil, nil
410	}
411	return parseEthAddr(addrStr)
412}
413
414func parseParamEthAddr(name string, c paramer) (*ethCommon.Address, error) {
415	addrStr := c.Param(name)
416	if addrStr == "" {
417		return nil, nil
418	}
419	return parseEthAddr(addrStr)
420}
421
422func parseEthAddr(ethAddrStr string) (*ethCommon.Address, error) {
423	var addr ethCommon.Address
424	err := addr.UnmarshalText([]byte(ethAddrStr))
425	return &addr, err
426}
427
428func parseParamHezEthAddr(c paramer) (*ethCommon.Address, error) {
429	const name = "hermezEthereumAddress"
430	addrStr := c.Param(name)
431	return hezStringToEthAddr(addrStr, name)
432}
433
434type errorMsg struct {
435	Message string
436}
437
438func bjjToString(bjj *babyjub.PublicKey) string {
439	pkComp := [32]byte(bjj.Compress())
440	sum := pkComp[0]
441	for i := 1; i < len(pkComp); i++ {
442		sum += pkComp[i]
443	}
444	bjjSum := append(pkComp[:], sum)
445	return "hez:" + base64.RawURLEncoding.EncodeToString(bjjSum)
446}
447
448func ethAddrToHez(addr ethCommon.Address) string {
449	return "hez:" + addr.String()
450}
451
452func idxToHez(idx common.Idx, tokenSymbol string) string {
453	if idx == 1 {
454		return exitIdx
455	}
456	return "hez:" + tokenSymbol + ":" + strconv.Itoa(int(idx))
457}