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