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