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