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 := ðCommon.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 ðSignature
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}