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.

334 lines
11 KiB

  1. package api
  2. import (
  3. "bytes"
  4. "crypto/ecdsa"
  5. "encoding/binary"
  6. "encoding/hex"
  7. "encoding/json"
  8. "math/big"
  9. "testing"
  10. "time"
  11. ethCrypto "github.com/ethereum/go-ethereum/crypto"
  12. "github.com/hermeznetwork/hermez-node/common"
  13. "github.com/hermeznetwork/hermez-node/db/historydb"
  14. "github.com/iden3/go-iden3-crypto/babyjub"
  15. "github.com/stretchr/testify/assert"
  16. "github.com/stretchr/testify/require"
  17. )
  18. // testPoolTxReceive is a struct to be used to assert the response
  19. // of GET /transactions-pool/:id
  20. type testPoolTxReceive struct {
  21. TxID common.TxID `json:"id"`
  22. Type common.TxType `json:"type"`
  23. FromIdx string `json:"fromAccountIndex"`
  24. FromEthAddr *string `json:"fromHezEthereumAddress"`
  25. FromBJJ *string `json:"fromBJJ"`
  26. ToIdx *string `json:"toAccountIndex"`
  27. ToEthAddr *string `json:"toHezEthereumAddress"`
  28. ToBJJ *string `json:"toBjj"`
  29. Amount string `json:"amount"`
  30. Fee common.FeeSelector `json:"fee"`
  31. Nonce common.Nonce `json:"nonce"`
  32. State common.PoolL2TxState `json:"state"`
  33. Signature babyjub.SignatureComp `json:"signature"`
  34. RqFromIdx *string `json:"requestFromAccountIndex"`
  35. RqToIdx *string `json:"requestToAccountIndex"`
  36. RqToEthAddr *string `json:"requestToHezEthereumAddress"`
  37. RqToBJJ *string `json:"requestToBJJ"`
  38. RqTokenID *common.TokenID `json:"requestTokenId"`
  39. RqAmount *string `json:"requestAmount"`
  40. RqFee *common.FeeSelector `json:"requestFee"`
  41. RqNonce *common.Nonce `json:"requestNonce"`
  42. BatchNum *common.BatchNum `json:"batchNum"`
  43. Timestamp time.Time `json:"timestamp"`
  44. Token historydb.TokenWithUSD `json:"token"`
  45. }
  46. // testPoolTxSend is a struct to be used as a JSON body
  47. // when testing POST /transactions-pool
  48. type testPoolTxSend struct {
  49. TxID common.TxID `json:"id" binding:"required"`
  50. Type common.TxType `json:"type" binding:"required"`
  51. TokenID common.TokenID `json:"tokenId"`
  52. FromIdx string `json:"fromAccountIndex" binding:"required"`
  53. ToIdx *string `json:"toAccountIndex"`
  54. ToEthAddr *string `json:"toHezEthereumAddress"`
  55. ToBJJ *string `json:"toBjj"`
  56. Amount string `json:"amount" binding:"required"`
  57. Fee common.FeeSelector `json:"fee"`
  58. Nonce common.Nonce `json:"nonce"`
  59. Signature babyjub.SignatureComp `json:"signature" binding:"required"`
  60. RqFromIdx *string `json:"requestFromAccountIndex"`
  61. RqToIdx *string `json:"requestToAccountIndex"`
  62. RqToEthAddr *string `json:"requestToHezEthereumAddress"`
  63. RqToBJJ *string `json:"requestToBjj"`
  64. RqTokenID *common.TokenID `json:"requestTokenId"`
  65. RqAmount *string `json:"requestAmount"`
  66. RqFee *common.FeeSelector `json:"requestFee"`
  67. RqNonce *common.Nonce `json:"requestNonce"`
  68. }
  69. func genTestPoolTxs(
  70. poolTxs []common.PoolL2Tx,
  71. tokens []historydb.TokenWithUSD,
  72. accs []common.Account,
  73. ) (poolTxsToSend []testPoolTxSend, poolTxsToReceive []testPoolTxReceive) {
  74. poolTxsToSend = []testPoolTxSend{}
  75. poolTxsToReceive = []testPoolTxReceive{}
  76. for _, poolTx := range poolTxs {
  77. // common.PoolL2Tx ==> testPoolTxSend
  78. token := getTokenByID(poolTx.TokenID, tokens)
  79. genSendTx := testPoolTxSend{
  80. TxID: poolTx.TxID,
  81. Type: poolTx.Type,
  82. TokenID: poolTx.TokenID,
  83. FromIdx: idxToHez(poolTx.FromIdx, token.Symbol),
  84. Amount: poolTx.Amount.String(),
  85. Fee: poolTx.Fee,
  86. Nonce: poolTx.Nonce,
  87. Signature: poolTx.Signature,
  88. RqFee: &poolTx.RqFee,
  89. RqNonce: &poolTx.RqNonce,
  90. }
  91. // common.PoolL2Tx ==> testPoolTxReceive
  92. genReceiveTx := testPoolTxReceive{
  93. TxID: poolTx.TxID,
  94. Type: poolTx.Type,
  95. FromIdx: idxToHez(poolTx.FromIdx, token.Symbol),
  96. Amount: poolTx.Amount.String(),
  97. Fee: poolTx.Fee,
  98. Nonce: poolTx.Nonce,
  99. State: poolTx.State,
  100. Signature: poolTx.Signature,
  101. Timestamp: poolTx.Timestamp,
  102. // BatchNum: poolTx.BatchNum,
  103. RqFee: &poolTx.RqFee,
  104. RqNonce: &poolTx.RqNonce,
  105. Token: token,
  106. }
  107. fromAcc := getAccountByIdx(poolTx.FromIdx, accs)
  108. fromAddr := ethAddrToHez(fromAcc.EthAddr)
  109. genReceiveTx.FromEthAddr = &fromAddr
  110. fromBjj := bjjToString(fromAcc.BJJ)
  111. genReceiveTx.FromBJJ = &fromBjj
  112. if poolTx.ToIdx != 0 {
  113. toIdx := idxToHez(poolTx.ToIdx, token.Symbol)
  114. genSendTx.ToIdx = &toIdx
  115. genReceiveTx.ToIdx = &toIdx
  116. }
  117. if poolTx.ToEthAddr != common.EmptyAddr {
  118. toEth := ethAddrToHez(poolTx.ToEthAddr)
  119. genSendTx.ToEthAddr = &toEth
  120. genReceiveTx.ToEthAddr = &toEth
  121. } else if poolTx.ToIdx > 255 {
  122. acc := getAccountByIdx(poolTx.ToIdx, accs)
  123. addr := ethAddrToHez(acc.EthAddr)
  124. genReceiveTx.ToEthAddr = &addr
  125. }
  126. if poolTx.ToBJJ != common.EmptyBJJComp {
  127. toBJJ := bjjToString(poolTx.ToBJJ)
  128. genSendTx.ToBJJ = &toBJJ
  129. genReceiveTx.ToBJJ = &toBJJ
  130. } else if poolTx.ToIdx > 255 {
  131. acc := getAccountByIdx(poolTx.ToIdx, accs)
  132. bjj := bjjToString(acc.BJJ)
  133. genReceiveTx.ToBJJ = &bjj
  134. }
  135. if poolTx.RqFromIdx != 0 {
  136. rqFromIdx := idxToHez(poolTx.RqFromIdx, token.Symbol)
  137. genSendTx.RqFromIdx = &rqFromIdx
  138. genReceiveTx.RqFromIdx = &rqFromIdx
  139. genSendTx.RqTokenID = &token.TokenID
  140. genReceiveTx.RqTokenID = &token.TokenID
  141. rqAmount := poolTx.RqAmount.String()
  142. genSendTx.RqAmount = &rqAmount
  143. genReceiveTx.RqAmount = &rqAmount
  144. if poolTx.RqToIdx != 0 {
  145. rqToIdx := idxToHez(poolTx.RqToIdx, token.Symbol)
  146. genSendTx.RqToIdx = &rqToIdx
  147. genReceiveTx.RqToIdx = &rqToIdx
  148. }
  149. if poolTx.RqToEthAddr != common.EmptyAddr {
  150. rqToEth := ethAddrToHez(poolTx.RqToEthAddr)
  151. genSendTx.RqToEthAddr = &rqToEth
  152. genReceiveTx.RqToEthAddr = &rqToEth
  153. }
  154. if poolTx.RqToBJJ != common.EmptyBJJComp {
  155. rqToBJJ := bjjToString(poolTx.RqToBJJ)
  156. genSendTx.RqToBJJ = &rqToBJJ
  157. genReceiveTx.RqToBJJ = &rqToBJJ
  158. }
  159. }
  160. poolTxsToSend = append(poolTxsToSend, genSendTx)
  161. poolTxsToReceive = append(poolTxsToReceive, genReceiveTx)
  162. }
  163. return poolTxsToSend, poolTxsToReceive
  164. }
  165. func TestPoolTxs(t *testing.T) {
  166. // POST
  167. endpoint := apiURL + "transactions-pool"
  168. fetchedTxID := common.TxID{}
  169. for _, tx := range tc.poolTxsToSend {
  170. jsonTxBytes, err := json.Marshal(tx)
  171. require.NoError(t, err)
  172. jsonTxReader := bytes.NewReader(jsonTxBytes)
  173. require.NoError(
  174. t, doGoodReq(
  175. "POST",
  176. endpoint,
  177. jsonTxReader, &fetchedTxID,
  178. ),
  179. )
  180. assert.Equal(t, tx.TxID, fetchedTxID)
  181. }
  182. // 400
  183. // Wrong fee
  184. badTx := tc.poolTxsToSend[0]
  185. badTx.Amount = "99950000000000000"
  186. badTx.Fee = 255
  187. jsonTxBytes, err := json.Marshal(badTx)
  188. require.NoError(t, err)
  189. jsonTxReader := bytes.NewReader(jsonTxBytes)
  190. err = doBadReq("POST", endpoint, jsonTxReader, 400)
  191. require.NoError(t, err)
  192. // Wrong signature
  193. badTx = tc.poolTxsToSend[0]
  194. badTx.FromIdx = "hez:foo:1000"
  195. jsonTxBytes, err = json.Marshal(badTx)
  196. require.NoError(t, err)
  197. jsonTxReader = bytes.NewReader(jsonTxBytes)
  198. err = doBadReq("POST", endpoint, jsonTxReader, 400)
  199. require.NoError(t, err)
  200. // Wrong to
  201. badTx = tc.poolTxsToSend[0]
  202. ethAddr := "hez:0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
  203. badTx.ToEthAddr = &ethAddr
  204. badTx.ToIdx = nil
  205. jsonTxBytes, err = json.Marshal(badTx)
  206. require.NoError(t, err)
  207. jsonTxReader = bytes.NewReader(jsonTxBytes)
  208. err = doBadReq("POST", endpoint, jsonTxReader, 400)
  209. require.NoError(t, err)
  210. // Wrong rq
  211. badTx = tc.poolTxsToSend[0]
  212. rqFromIdx := "hez:foo:30"
  213. badTx.RqFromIdx = &rqFromIdx
  214. jsonTxBytes, err = json.Marshal(badTx)
  215. require.NoError(t, err)
  216. jsonTxReader = bytes.NewReader(jsonTxBytes)
  217. err = doBadReq("POST", endpoint, jsonTxReader, 400)
  218. require.NoError(t, err)
  219. // GET
  220. endpoint += "/"
  221. for _, tx := range tc.poolTxsToReceive {
  222. fetchedTx := testPoolTxReceive{}
  223. require.NoError(
  224. t, doGoodReq(
  225. "GET",
  226. endpoint+tx.TxID.String(),
  227. nil, &fetchedTx,
  228. ),
  229. )
  230. assertPoolTx(t, tx, fetchedTx)
  231. }
  232. // 400, due invalid TxID
  233. err = doBadReq("GET", endpoint+"0xG2241b6f2b1dd772dba391f4a1a3407c7c21f598d86e2585a14e616fb4a255f823", nil, 400)
  234. require.NoError(t, err)
  235. // 404, due nonexistent TxID in DB
  236. err = doBadReq("GET", endpoint+"0x02241b6f2b1dd772dba391f4a1a3407c7c21f598d86e2585a14e616fb4a255f823", nil, 404)
  237. require.NoError(t, err)
  238. }
  239. func assertPoolTx(t *testing.T, expected, actual testPoolTxReceive) {
  240. // state should be pending
  241. assert.Equal(t, common.PoolL2TxStatePending, actual.State)
  242. expected.State = actual.State
  243. actual.Token.ItemID = 0
  244. // timestamp should be very close to now
  245. assert.Less(t, time.Now().UTC().Unix()-3, actual.Timestamp.Unix())
  246. expected.Timestamp = actual.Timestamp
  247. // token timestamp
  248. if expected.Token.USDUpdate == nil {
  249. assert.Equal(t, expected.Token.USDUpdate, actual.Token.USDUpdate)
  250. } else {
  251. assert.Equal(t, expected.Token.USDUpdate.Unix(), actual.Token.USDUpdate.Unix())
  252. expected.Token.USDUpdate = actual.Token.USDUpdate
  253. }
  254. assert.Equal(t, expected, actual)
  255. }
  256. // TestAllTosNull test that the API doesn't accept txs with all the TOs set to null (to eth, to bjj, to idx)
  257. func TestAllTosNull(t *testing.T) {
  258. // Generate account:
  259. // Ethereum private key
  260. var key ecdsa.PrivateKey
  261. key.D = big.NewInt(int64(4444)) // only for testing
  262. key.PublicKey.X, key.PublicKey.Y = ethCrypto.S256().ScalarBaseMult(key.D.Bytes())
  263. key.Curve = ethCrypto.S256()
  264. addr := ethCrypto.PubkeyToAddress(key.PublicKey)
  265. // BJJ private key
  266. var sk babyjub.PrivateKey
  267. var iBytes [8]byte
  268. binary.LittleEndian.PutUint64(iBytes[:], 4444)
  269. copy(sk[:], iBytes[:]) // only for testing
  270. account := common.Account{
  271. Idx: 4444,
  272. TokenID: 0,
  273. BatchNum: 1,
  274. BJJ: sk.Public().Compress(),
  275. EthAddr: addr,
  276. Nonce: 0,
  277. Balance: big.NewInt(1000000),
  278. }
  279. // Add account to history DB (required to verify signature)
  280. err := api.h.AddAccounts([]common.Account{account})
  281. assert.NoError(t, err)
  282. // Genrate tx with all tos set to nil (to eth, to bjj, to idx)
  283. tx := common.PoolL2Tx{
  284. FromIdx: account.Idx,
  285. TokenID: account.TokenID,
  286. Amount: big.NewInt(1000),
  287. Fee: 200,
  288. Nonce: 0,
  289. }
  290. // Set idx and type manually, and check that the function doesn't allow it
  291. _, err = common.NewPoolL2Tx(&tx)
  292. assert.Error(t, err)
  293. tx.Type = common.TxTypeTransfer
  294. var txID common.TxID
  295. txIDRaw, err := hex.DecodeString("02e66e24f7f25272906647c8fd1d7fe8acf3cf3e9b38ffc9f94bbb5090dc275073")
  296. assert.NoError(t, err)
  297. copy(txID[:], txIDRaw)
  298. tx.TxID = txID
  299. // Sign tx
  300. toSign, err := tx.HashToSign(0)
  301. assert.NoError(t, err)
  302. sig := sk.SignPoseidon(toSign)
  303. tx.Signature = sig.Compress()
  304. // Transform common.PoolL2Tx ==> testPoolTxSend
  305. txToSend := testPoolTxSend{
  306. TxID: tx.TxID,
  307. Type: tx.Type,
  308. TokenID: tx.TokenID,
  309. FromIdx: idxToHez(tx.FromIdx, "ETH"),
  310. Amount: tx.Amount.String(),
  311. Fee: tx.Fee,
  312. Nonce: tx.Nonce,
  313. Signature: tx.Signature,
  314. }
  315. // Send tx to the API
  316. jsonTxBytes, err := json.Marshal(txToSend)
  317. require.NoError(t, err)
  318. jsonTxReader := bytes.NewReader(jsonTxBytes)
  319. err = doBadReq("POST", apiURL+"transactions-pool", jsonTxReader, 400)
  320. require.NoError(t, err)
  321. // Clean historyDB: the added account shouldn't be there for other tests
  322. _, err = api.h.DB().DB.Exec("delete from account where idx = 4444")
  323. assert.NoError(t, err)
  324. }