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 parseQueryBool(name string, dflt *bool, c querier) (*bool, error) { //nolint:SA4009 res may be not overwriten
 58	str := c.Query(name)
 59	if str == "" {
 60		return dflt, nil
 61	}
 62	if str == "true" {
 63		res := new(bool)
 64		*res = true
 65		return res, nil
 66	}
 67	if str == "false" {
 68		res := new(bool)
 69		*res = false
 70		return res, nil
 71	}
 72	return nil, fmt.Errorf("Inavlid %s. Must be eithe true or false", name)
 73}
 74
 75func parseQueryHezEthAddr(c querier) (*ethCommon.Address, error) {
 76	const name = "hermezEthereumAddress"
 77	addrStr := c.Query(name)
 78	if addrStr == "" {
 79		return nil, nil
 80	}
 81	splitted := strings.Split(addrStr, "hez:")
 82	if len(splitted) != 2 || len(splitted[1]) != 42 {
 83		return nil, fmt.Errorf(
 84			"Invalid %s, must follow this regex: ^hez:0x[a-fA-F0-9]{40}$", name)
 85	}
 86	var addr ethCommon.Address
 87	err := addr.UnmarshalText([]byte(splitted[1]))
 88	return &addr, err
 89}
 90
 91func parseQueryBJJ(c querier) (*babyjub.PublicKey, error) {
 92	const name = "BJJ"
 93	const decodedLen = 33
 94	bjjStr := c.Query(name)
 95	if bjjStr == "" {
 96		return nil, nil
 97	}
 98	splitted := strings.Split(bjjStr, "hez:")
 99	if len(splitted) != 2 || len(splitted[1]) != 44 {
100		return nil, fmt.Errorf(
101			"Invalid %s, must follow this regex: ^hez:[A-Za-z0-9+/=]{44}$",
102			name)
103	}
104	decoded, err := base64.RawURLEncoding.DecodeString(splitted[1])
105	if err != nil {
106		return nil, fmt.Errorf(
107			"Invalid %s, error decoding base64 string: %s",
108			name, err.Error())
109	}
110	if len(decoded) != decodedLen {
111		return nil, fmt.Errorf(
112			"invalid %s, error decoding base64 string: unexpected byte array length",
113			name)
114	}
115	bjjBytes := [decodedLen - 1]byte{}
116	copy(bjjBytes[:decodedLen-1], decoded[:decodedLen-1])
117	sum := bjjBytes[0]
118	for i := 1; i < len(bjjBytes); i++ {
119		sum += bjjBytes[i]
120	}
121	if decoded[decodedLen-1] != sum {
122		return nil, fmt.Errorf("invalid %s, checksum failed",
123			name)
124	}
125	bjjComp := babyjub.PublicKeyComp(bjjBytes)
126	bjj, err := bjjComp.Decompress()
127	if err != nil {
128		return nil, fmt.Errorf(
129			"invalid %s, error decompressing public key: %s",
130			name, err.Error())
131	}
132	return bjj, nil
133}
134
135func parseQueryTxType(c querier) (*common.TxType, error) {
136	const name = "type"
137	typeStr := c.Query(name)
138	if typeStr == "" {
139		return nil, nil
140	}
141	switch common.TxType(typeStr) {
142	case common.TxTypeExit:
143		ret := common.TxTypeExit
144		return &ret, nil
145	case common.TxTypeTransfer:
146		ret := common.TxTypeTransfer
147		return &ret, nil
148	case common.TxTypeDeposit:
149		ret := common.TxTypeDeposit
150		return &ret, nil
151	case common.TxTypeCreateAccountDeposit:
152		ret := common.TxTypeCreateAccountDeposit
153		return &ret, nil
154	case common.TxTypeCreateAccountDepositTransfer:
155		ret := common.TxTypeCreateAccountDepositTransfer
156		return &ret, nil
157	case common.TxTypeDepositTransfer:
158		ret := common.TxTypeDepositTransfer
159		return &ret, nil
160	case common.TxTypeForceTransfer:
161		ret := common.TxTypeForceTransfer
162		return &ret, nil
163	case common.TxTypeForceExit:
164		ret := common.TxTypeForceExit
165		return &ret, nil
166	case common.TxTypeTransferToEthAddr:
167		ret := common.TxTypeTransferToEthAddr
168		return &ret, nil
169	case common.TxTypeTransferToBJJ:
170		ret := common.TxTypeTransferToBJJ
171		return &ret, nil
172	}
173	return nil, fmt.Errorf(
174		"invalid %s, %s is not a valid option. Check the valid options in the docmentation",
175		name, typeStr,
176	)
177}
178
179func parseIdx(c querier) (*common.Idx, error) {
180	const name = "accountIndex"
181	idxStr := c.Query(name)
182	return stringToIdx(idxStr, name)
183}
184
185func parseAccountFilters(c querier) (*common.TokenID, *ethCommon.Address, *babyjub.PublicKey, *common.Idx, error) {
186	// TokenID
187	tid, err := parseQueryUint("tokenId", nil, 0, maxUint32, c)
188	if err != nil {
189		return nil, nil, nil, nil, err
190	}
191	var tokenID *common.TokenID
192	if tid != nil {
193		tokenID = new(common.TokenID)
194		*tokenID = common.TokenID(*tid)
195	}
196	// Hez Eth addr
197	addr, err := parseQueryHezEthAddr(c)
198	if err != nil {
199		return nil, nil, nil, nil, err
200	}
201	// BJJ
202	bjj, err := parseQueryBJJ(c)
203	if err != nil {
204		return nil, nil, nil, nil, err
205	}
206	if addr != nil && bjj != nil {
207		return nil, nil, nil, nil,
208			errors.New("bjj and hermezEthereumAddress params are incompatible")
209	}
210	// Idx
211	idx, err := parseIdx(c)
212	if err != nil {
213		return nil, nil, nil, nil, err
214	}
215	if idx != nil && (addr != nil || bjj != nil || tokenID != nil) {
216		return nil, nil, nil, nil,
217			errors.New("accountIndex is incompatible with BJJ, hermezEthereumAddress and tokenId")
218	}
219	return tokenID, addr, bjj, idx, nil
220}
221
222// Param parsers
223
224type paramer interface {
225	Param(string) string
226}
227
228func parseParamTxID(c paramer) (common.TxID, error) {
229	const name = "id"
230	txIDStr := c.Param(name)
231	if txIDStr == "" {
232		return common.TxID{}, fmt.Errorf("%s is required", name)
233	}
234	txID, err := common.NewTxIDFromString(txIDStr)
235	if err != nil {
236		return common.TxID{}, fmt.Errorf("invalid %s", name)
237	}
238	return txID, nil
239}
240
241func parseParamIdx(c paramer) (*common.Idx, error) {
242	const name = "accountIndex"
243	idxStr := c.Param(name)
244	return stringToIdx(idxStr, name)
245}
246
247func parseParamUint(name string, dflt *uint, min, max uint, c paramer) (*uint, error) { //nolint:SA4009 res may be not overwriten
248	str := c.Param(name)
249	return stringToUint(str, name, dflt, min, max)
250}
251
252func stringToIdx(idxStr, name string) (*common.Idx, error) {
253	if idxStr == "" {
254		return nil, nil
255	}
256	splitted := strings.Split(idxStr, ":")
257	const expectedLen = 3
258	if len(splitted) != expectedLen || splitted[0] != "hez" {
259		return nil, fmt.Errorf(
260			"invalid %s, must follow this: hez:<tokenSymbol>:index", name)
261	}
262	// TODO: check that the tokenSymbol match the token related to the account index
263	idxInt, err := strconv.Atoi(splitted[2])
264	idx := common.Idx(idxInt)
265	return &idx, err
266}
267
268func stringToUint(uintStr, name string, dflt *uint, min, max uint) (*uint, error) {
269	if uintStr != "" {
270		resInt, err := strconv.Atoi(uintStr)
271		if err != nil || resInt < 0 || resInt < int(min) || resInt > int(max) {
272			return nil, fmt.Errorf(
273				"Inavlid %s. Must be an integer within the range [%d, %d]",
274				name, min, max)
275		}
276		res := uint(resInt)
277		return &res, nil
278	}
279	return dflt, nil
280}