arnaucube
arnaucube/hermez-nodePublic Self-hosted repository
hermez-node/api/apitypes/apitypes.go
api/apitypes/apitypes.goRaw
  1/*
  2Package apitypes is used to map the common types used across the node with the format expected by the API.
  3
  4This is done using different strategies:
  5- Marshallers: they get triggered when the API marshals the response structs into JSONs
  6- Scanners/Valuers: they get triggered when a struct is sent/received to/from the SQL database
  7- Adhoc functions: when the already mentioned strategies are not suitable, functions are added to the structs to facilitate the conversions
  8*/
  9package apitypes
 10
 11import (
 12	"database/sql/driver"
 13	"encoding/base64"
 14	"encoding/hex"
 15	"errors"
 16	"fmt"
 17	"math/big"
 18	"strconv"
 19	"strings"
 20
 21	ethCommon "github.com/ethereum/go-ethereum/common"
 22	"github.com/hermeznetwork/hermez-node/common"
 23	"github.com/hermeznetwork/tracerr"
 24	"github.com/iden3/go-iden3-crypto/babyjub"
 25)
 26
 27// BigIntStr is used to scan/value *big.Int directly into strings from/to sql DBs.
 28// It assumes that *big.Int are inserted/fetched to/from the DB using the BigIntMeddler meddler
 29// defined at github.com/hermeznetwork/hermez-node/db.  Since *big.Int is
 30// stored as DECIMAL in SQL, there's no need to implement Scan()/Value()
 31// because DECIMALS are encoded/decoded as strings by the sql driver, and
 32// BigIntStr is already a string.
 33type BigIntStr string
 34
 35// NewBigIntStr creates a *BigIntStr from a *big.Int.
 36// If the provided bigInt is nil the returned *BigIntStr will also be nil
 37func NewBigIntStr(bigInt *big.Int) *BigIntStr {
 38	if bigInt == nil {
 39		return nil
 40	}
 41	bigIntStr := BigIntStr(bigInt.String())
 42	return &bigIntStr
 43}
 44
 45// StrBigInt is used to unmarshal BigIntStr directly into an alias of big.Int
 46type StrBigInt big.Int
 47
 48// UnmarshalText unmarshals a StrBigInt
 49func (s *StrBigInt) UnmarshalText(text []byte) error {
 50	bi, ok := (*big.Int)(s).SetString(string(text), 10)
 51	if !ok {
 52		return tracerr.Wrap(fmt.Errorf("could not unmarshal %s into a StrBigInt", text))
 53	}
 54	*s = StrBigInt(*bi)
 55	return nil
 56}
 57
 58// CollectedFeesAPI is send common.batch.CollectedFee through the API
 59type CollectedFeesAPI map[common.TokenID]BigIntStr
 60
 61// NewCollectedFeesAPI creates a new CollectedFeesAPI from a *big.Int map
 62func NewCollectedFeesAPI(m map[common.TokenID]*big.Int) CollectedFeesAPI {
 63	c := CollectedFeesAPI(make(map[common.TokenID]BigIntStr))
 64	for k, v := range m {
 65		c[k] = *NewBigIntStr(v)
 66	}
 67	return c
 68}
 69
 70// HezEthAddr is used to scan/value Ethereum Address directly into strings that follow the Ethereum address hez format (^hez:0x[a-fA-F0-9]{40}$) from/to sql DBs.
 71// It assumes that Ethereum Address are inserted/fetched to/from the DB using the default Scan/Value interface
 72type HezEthAddr string
 73
 74// NewHezEthAddr creates a HezEthAddr from an Ethereum addr
 75func NewHezEthAddr(addr ethCommon.Address) HezEthAddr {
 76	return HezEthAddr("hez:" + addr.String())
 77}
 78
 79// ToEthAddr returns an Ethereum Address created from HezEthAddr
 80func (a HezEthAddr) ToEthAddr() (ethCommon.Address, error) {
 81	addrStr := strings.TrimPrefix(string(a), "hez:")
 82	var addr ethCommon.Address
 83	return addr, addr.UnmarshalText([]byte(addrStr))
 84}
 85
 86// Scan implements Scanner for database/sql
 87func (a *HezEthAddr) Scan(src interface{}) error {
 88	ethAddr := &ethCommon.Address{}
 89	if err := ethAddr.Scan(src); err != nil {
 90		return tracerr.Wrap(err)
 91	}
 92	if ethAddr == nil {
 93		return nil
 94	}
 95	*a = NewHezEthAddr(*ethAddr)
 96	return nil
 97}
 98
 99// Value implements valuer for database/sql
100func (a HezEthAddr) Value() (driver.Value, error) {
101	ethAddr, err := a.ToEthAddr()
102	if err != nil {
103		return nil, tracerr.Wrap(err)
104	}
105	return ethAddr.Value()
106}
107
108// StrHezEthAddr is used to unmarshal HezEthAddr directly into an alias of ethCommon.Address
109type StrHezEthAddr ethCommon.Address
110
111// UnmarshalText unmarshals a StrHezEthAddr
112func (s *StrHezEthAddr) UnmarshalText(text []byte) error {
113	withoutHez := strings.TrimPrefix(string(text), "hez:")
114	var addr ethCommon.Address
115	if err := addr.UnmarshalText([]byte(withoutHez)); err != nil {
116		return tracerr.Wrap(err)
117	}
118	*s = StrHezEthAddr(addr)
119	return nil
120}
121
122// HezBJJ is used to scan/value *babyjub.PublicKeyComp directly into strings that follow the BJJ public key hez format (^hez:[A-Za-z0-9_-]{44}$) from/to sql DBs.
123// It assumes that *babyjub.PublicKeyComp are inserted/fetched to/from the DB using the default Scan/Value interface
124type HezBJJ string
125
126// NewHezBJJ creates a HezBJJ from a *babyjub.PublicKeyComp.
127// Calling this method with a nil bjj causes panic
128func NewHezBJJ(pkComp babyjub.PublicKeyComp) HezBJJ {
129	sum := pkComp[0]
130	for i := 1; i < len(pkComp); i++ {
131		sum += pkComp[i]
132	}
133	bjjSum := append(pkComp[:], sum)
134	return HezBJJ("hez:" + base64.RawURLEncoding.EncodeToString(bjjSum))
135}
136
137func hezStrToBJJ(s string) (babyjub.PublicKeyComp, error) {
138	const decodedLen = 33
139	const encodedLen = 44
140	formatErr := errors.New("invalid BJJ format. Must follow this regex: ^hez:[A-Za-z0-9_-]{44}$")
141	encoded := strings.TrimPrefix(s, "hez:")
142	if len(encoded) != encodedLen {
143		return common.EmptyBJJComp, formatErr
144	}
145	decoded, err := base64.RawURLEncoding.DecodeString(encoded)
146	if err != nil {
147		return common.EmptyBJJComp, formatErr
148	}
149	if len(decoded) != decodedLen {
150		return common.EmptyBJJComp, formatErr
151	}
152	bjjBytes := [decodedLen - 1]byte{}
153	copy(bjjBytes[:decodedLen-1], decoded[:decodedLen-1])
154	sum := bjjBytes[0]
155	for i := 1; i < len(bjjBytes); i++ {
156		sum += bjjBytes[i]
157	}
158	if decoded[decodedLen-1] != sum {
159		return common.EmptyBJJComp, tracerr.Wrap(errors.New("checksum verification failed"))
160	}
161	bjjComp := babyjub.PublicKeyComp(bjjBytes)
162	return bjjComp, nil
163}
164
165// ToBJJ returns a babyjub.PublicKeyComp created from HezBJJ
166func (b HezBJJ) ToBJJ() (babyjub.PublicKeyComp, error) {
167	return hezStrToBJJ(string(b))
168}
169
170// Scan implements Scanner for database/sql
171func (b *HezBJJ) Scan(src interface{}) error {
172	bjj := &babyjub.PublicKeyComp{}
173	if err := bjj.Scan(src); err != nil {
174		return tracerr.Wrap(err)
175	}
176	if bjj == nil {
177		return nil
178	}
179	*b = NewHezBJJ(*bjj)
180	return nil
181}
182
183// Value implements valuer for database/sql
184func (b HezBJJ) Value() (driver.Value, error) {
185	bjj, err := b.ToBJJ()
186	if err != nil {
187		return nil, tracerr.Wrap(err)
188	}
189	return bjj.Value()
190}
191
192// StrHezBJJ is used to unmarshal HezBJJ directly into an alias of babyjub.PublicKeyComp
193type StrHezBJJ babyjub.PublicKeyComp
194
195// UnmarshalText unmarshalls a StrHezBJJ
196func (s *StrHezBJJ) UnmarshalText(text []byte) error {
197	bjj, err := hezStrToBJJ(string(text))
198	if err != nil {
199		return tracerr.Wrap(err)
200	}
201	*s = StrHezBJJ(bjj)
202	return nil
203}
204
205// HezIdx is used to value common.Idx directly into strings that follow the Idx key hez format (hez:tokenSymbol:idx) to sql DBs.
206// Note that this can only be used to insert to DB since there is no way to automatically read from the DB since it needs the tokenSymbol
207type HezIdx string
208
209// StrHezIdx is used to unmarshal HezIdx directly into an alias of common.Idx
210type StrHezIdx common.Idx
211
212// UnmarshalText unmarshals a StrHezIdx
213func (s *StrHezIdx) UnmarshalText(text []byte) error {
214	withoutHez := strings.TrimPrefix(string(text), "hez:")
215	splitted := strings.Split(withoutHez, ":")
216	const expectedLen = 2
217	if len(splitted) != expectedLen {
218		return tracerr.Wrap(fmt.Errorf("can not unmarshal %s into StrHezIdx", text))
219	}
220	idxInt, err := strconv.Atoi(splitted[1])
221	if err != nil {
222		return tracerr.Wrap(err)
223	}
224	*s = StrHezIdx(common.Idx(idxInt))
225	return nil
226}
227
228// EthSignature is used to scan/value []byte representing an Ethereum signature directly into strings from/to sql DBs.
229type EthSignature string
230
231// NewEthSignature creates a *EthSignature from []byte
232// If the provided signature is nil the returned *EthSignature will also be nil
233func NewEthSignature(signature []byte) *EthSignature {
234	if signature == nil {
235		return nil
236	}
237	ethSignature := EthSignature("0x" + hex.EncodeToString(signature))
238	return &ethSignature
239}
240
241// Scan implements Scanner for database/sql
242func (e *EthSignature) Scan(src interface{}) error {
243	if srcStr, ok := src.(string); ok {
244		// src is a string
245		*e = *(NewEthSignature([]byte(srcStr)))
246		return nil
247	} else if srcBytes, ok := src.([]byte); ok {
248		// src is []byte
249		*e = *(NewEthSignature(srcBytes))
250		return nil
251	} else {
252		// unexpected src
253		return tracerr.Wrap(fmt.Errorf("can't scan %T into apitypes.EthSignature", src))
254	}
255}
256
257// Value implements valuer for database/sql
258func (e EthSignature) Value() (driver.Value, error) {
259	without0x := strings.TrimPrefix(string(e), "0x")
260	return hex.DecodeString(without0x)
261}
262
263// UnmarshalText unmarshals a StrEthSignature
264func (e *EthSignature) UnmarshalText(text []byte) error {
265	without0x := strings.TrimPrefix(string(text), "0x")
266	signature, err := hex.DecodeString(without0x)
267	if err != nil {
268		return tracerr.Wrap(err)
269	}
270	*e = EthSignature([]byte(signature))
271	return nil
272}