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