You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

272 lines
8.5 KiB

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