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