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.

513 lines
15 KiB

  1. package api
  2. import (
  3. "fmt"
  4. "math"
  5. "math/big"
  6. "testing"
  7. "time"
  8. "github.com/hermeznetwork/hermez-node/apitypes"
  9. "github.com/hermeznetwork/hermez-node/common"
  10. "github.com/hermeznetwork/hermez-node/db"
  11. "github.com/hermeznetwork/hermez-node/db/historydb"
  12. "github.com/hermeznetwork/hermez-node/test"
  13. "github.com/mitchellh/copystructure"
  14. "github.com/stretchr/testify/assert"
  15. "github.com/stretchr/testify/require"
  16. )
  17. type testL1Info struct {
  18. ToForgeL1TxsNum *int64 `json:"toForgeL1TransactionsNum"`
  19. UserOrigin bool `json:"userOrigin"`
  20. LoadAmount string `json:"loadAmount"`
  21. HistoricLoadAmountUSD *float64 `json:"historicLoadAmountUSD"`
  22. EthBlockNum int64 `json:"ethereumBlockNum"`
  23. }
  24. type testL2Info struct {
  25. Fee common.FeeSelector `json:"fee"`
  26. HistoricFeeUSD *float64 `json:"historicFeeUSD"`
  27. Nonce common.Nonce `json:"nonce"`
  28. }
  29. type testTx struct {
  30. IsL1 string `json:"L1orL2"`
  31. TxID common.TxID `json:"id"`
  32. ItemID uint64 `json:"itemId"`
  33. Type common.TxType `json:"type"`
  34. Position int `json:"position"`
  35. FromIdx *string `json:"fromAccountIndex"`
  36. FromEthAddr *string `json:"fromHezEthereumAddress"`
  37. FromBJJ *string `json:"fromBJJ"`
  38. ToIdx string `json:"toAccountIndex"`
  39. ToEthAddr *string `json:"toHezEthereumAddress"`
  40. ToBJJ *string `json:"toBJJ"`
  41. Amount string `json:"amount"`
  42. BatchNum *common.BatchNum `json:"batchNum"`
  43. HistoricUSD *float64 `json:"historicUSD"`
  44. Timestamp time.Time `json:"timestamp"`
  45. L1Info *testL1Info `json:"L1Info"`
  46. L2Info *testL2Info `json:"L2Info"`
  47. Token historydb.TokenWithUSD `json:"token"`
  48. }
  49. type testTxsResponse struct {
  50. Txs []testTx `json:"transactions"`
  51. Pagination *db.Pagination `json:"pagination"`
  52. }
  53. func (t testTxsResponse) GetPagination() *db.Pagination {
  54. if t.Txs[0].ItemID < t.Txs[len(t.Txs)-1].ItemID {
  55. t.Pagination.FirstReturnedItem = t.Txs[0].ItemID
  56. t.Pagination.LastReturnedItem = t.Txs[len(t.Txs)-1].ItemID
  57. } else {
  58. t.Pagination.LastReturnedItem = t.Txs[0].ItemID
  59. t.Pagination.FirstReturnedItem = t.Txs[len(t.Txs)-1].ItemID
  60. }
  61. return t.Pagination
  62. }
  63. func (t testTxsResponse) Len() int {
  64. return len(t.Txs)
  65. }
  66. // TxSortFields represents the fields needed to sort L1 and L2 transactions
  67. type txSortFields struct {
  68. BatchNum *common.BatchNum
  69. Position int
  70. }
  71. // TxSortFielder is a interface that allows sorting L1 and L2 transactions in a combined way
  72. type txSortFielder interface {
  73. SortFields() txSortFields
  74. L1() *common.L1Tx
  75. L2() *common.L2Tx
  76. }
  77. // TxsSort array of TxSortFielder
  78. type txsSort []txSortFielder
  79. func (t txsSort) Len() int { return len(t) }
  80. func (t txsSort) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
  81. func (t txsSort) Less(i, j int) bool {
  82. // i not forged yet
  83. isf := t[i].SortFields()
  84. jsf := t[j].SortFields()
  85. if isf.BatchNum == nil {
  86. if jsf.BatchNum != nil { // j is already forged
  87. return false
  88. }
  89. // Both aren't forged, is i in a smaller position?
  90. return isf.Position < jsf.Position
  91. }
  92. // i is forged
  93. if jsf.BatchNum == nil {
  94. return false // j is not forged
  95. }
  96. // Both are forged
  97. if *isf.BatchNum == *jsf.BatchNum {
  98. // At the same batch, is i in a smaller position?
  99. return isf.Position < jsf.Position
  100. }
  101. // At different batches, is i in a smaller batch?
  102. return *isf.BatchNum < *jsf.BatchNum
  103. }
  104. type wrappedL1 common.L1Tx
  105. // SortFields implements TxSortFielder
  106. func (tx *wrappedL1) SortFields() txSortFields {
  107. return txSortFields{
  108. BatchNum: tx.BatchNum,
  109. Position: tx.Position,
  110. }
  111. }
  112. // L1 implements TxSortFielder
  113. func (tx *wrappedL1) L1() *common.L1Tx {
  114. l1tx := common.L1Tx(*tx)
  115. return &l1tx
  116. }
  117. // L2 implements TxSortFielder
  118. func (tx *wrappedL1) L2() *common.L2Tx { return nil }
  119. type wrappedL2 common.L2Tx
  120. // SortFields implements TxSortFielder
  121. func (tx *wrappedL2) SortFields() txSortFields {
  122. return txSortFields{
  123. BatchNum: &tx.BatchNum,
  124. Position: tx.Position,
  125. }
  126. }
  127. // L1 implements TxSortFielder
  128. func (tx *wrappedL2) L1() *common.L1Tx { return nil }
  129. // L2 implements TxSortFielder
  130. func (tx *wrappedL2) L2() *common.L2Tx {
  131. l2tx := common.L2Tx(*tx)
  132. return &l2tx
  133. }
  134. func genTestTxs(
  135. genericTxs []txSortFielder,
  136. usrIdxs []string,
  137. accs []common.Account,
  138. tokens []historydb.TokenWithUSD,
  139. blocks []common.Block,
  140. ) (usrTxs []testTx, allTxs []testTx) {
  141. usrTxs = []testTx{}
  142. allTxs = []testTx{}
  143. isUsrTx := func(tx testTx) bool {
  144. for _, idx := range usrIdxs {
  145. if tx.FromIdx != nil && *tx.FromIdx == idx {
  146. return true
  147. }
  148. if tx.ToIdx == idx {
  149. return true
  150. }
  151. }
  152. return false
  153. }
  154. for _, genericTx := range genericTxs {
  155. l1 := genericTx.L1()
  156. l2 := genericTx.L2()
  157. if l1 != nil { // L1Tx to testTx
  158. token := getTokenByID(l1.TokenID, tokens)
  159. // l1.FromEthAddr and l1.FromBJJ can't be nil
  160. fromEthAddr := string(apitypes.NewHezEthAddr(l1.FromEthAddr))
  161. fromBJJ := string(apitypes.NewHezBJJ(l1.FromBJJ))
  162. tx := testTx{
  163. IsL1: "L1",
  164. TxID: l1.TxID,
  165. Type: l1.Type,
  166. Position: l1.Position,
  167. FromEthAddr: &fromEthAddr,
  168. FromBJJ: &fromBJJ,
  169. ToIdx: idxToHez(l1.ToIdx, token.Symbol),
  170. Amount: l1.Amount.String(),
  171. BatchNum: l1.BatchNum,
  172. Timestamp: getTimestamp(l1.EthBlockNum, blocks),
  173. L1Info: &testL1Info{
  174. ToForgeL1TxsNum: l1.ToForgeL1TxsNum,
  175. UserOrigin: l1.UserOrigin,
  176. LoadAmount: l1.LoadAmount.String(),
  177. EthBlockNum: l1.EthBlockNum,
  178. },
  179. Token: token,
  180. }
  181. // If FromIdx is not nil
  182. if l1.FromIdx != 0 {
  183. idxStr := idxToHez(l1.FromIdx, token.Symbol)
  184. tx.FromIdx = &idxStr
  185. }
  186. // If tx has a normal ToIdx (>255), set FromEthAddr and FromBJJ
  187. if l1.ToIdx >= common.UserThreshold {
  188. // find account
  189. for _, acc := range accs {
  190. if l1.ToIdx == acc.Idx {
  191. toEthAddr := string(apitypes.NewHezEthAddr(acc.EthAddr))
  192. tx.ToEthAddr = &toEthAddr
  193. toBJJ := string(apitypes.NewHezBJJ(acc.PublicKey))
  194. tx.ToBJJ = &toBJJ
  195. break
  196. }
  197. }
  198. }
  199. // If the token has USD value setted
  200. if token.USD != nil {
  201. af := new(big.Float).SetInt(l1.Amount)
  202. amountFloat, _ := af.Float64()
  203. usd := *token.USD * amountFloat / math.Pow(10, float64(token.Decimals))
  204. tx.HistoricUSD = &usd
  205. laf := new(big.Float).SetInt(l1.LoadAmount)
  206. loadAmountFloat, _ := laf.Float64()
  207. loadUSD := *token.USD * loadAmountFloat / math.Pow(10, float64(token.Decimals))
  208. tx.L1Info.HistoricLoadAmountUSD = &loadUSD
  209. }
  210. allTxs = append(allTxs, tx)
  211. if isUsrTx(tx) {
  212. usrTxs = append(usrTxs, tx)
  213. }
  214. } else { // L2Tx to testTx
  215. token := getTokenByIdx(l2.FromIdx, tokens, accs)
  216. // l1.FromIdx can't be nil
  217. fromIdx := idxToHez(l2.FromIdx, token.Symbol)
  218. tx := testTx{
  219. IsL1: "L2",
  220. TxID: l2.TxID,
  221. Type: l2.Type,
  222. Position: l2.Position,
  223. ToIdx: idxToHez(l2.ToIdx, token.Symbol),
  224. FromIdx: &fromIdx,
  225. Amount: l2.Amount.String(),
  226. BatchNum: &l2.BatchNum,
  227. Timestamp: getTimestamp(l2.EthBlockNum, blocks),
  228. L2Info: &testL2Info{
  229. Nonce: l2.Nonce,
  230. Fee: l2.Fee,
  231. },
  232. Token: token,
  233. }
  234. // If FromIdx is not nil
  235. if l2.FromIdx != 0 {
  236. idxStr := idxToHez(l2.FromIdx, token.Symbol)
  237. tx.FromIdx = &idxStr
  238. }
  239. // Set FromEthAddr and FromBJJ (FromIdx it's always >255)
  240. for _, acc := range accs {
  241. if l2.ToIdx == acc.Idx {
  242. fromEthAddr := string(apitypes.NewHezEthAddr(acc.EthAddr))
  243. tx.FromEthAddr = &fromEthAddr
  244. fromBJJ := string(apitypes.NewHezBJJ(acc.PublicKey))
  245. tx.FromBJJ = &fromBJJ
  246. break
  247. }
  248. }
  249. // If tx has a normal ToIdx (>255), set FromEthAddr and FromBJJ
  250. if l2.ToIdx >= common.UserThreshold {
  251. // find account
  252. for _, acc := range accs {
  253. if l2.ToIdx == acc.Idx {
  254. toEthAddr := string(apitypes.NewHezEthAddr(acc.EthAddr))
  255. tx.ToEthAddr = &toEthAddr
  256. toBJJ := string(apitypes.NewHezBJJ(acc.PublicKey))
  257. tx.ToBJJ = &toBJJ
  258. break
  259. }
  260. }
  261. }
  262. // If the token has USD value setted
  263. if token.USD != nil {
  264. af := new(big.Float).SetInt(l2.Amount)
  265. amountFloat, _ := af.Float64()
  266. usd := *token.USD * amountFloat / math.Pow(10, float64(token.Decimals))
  267. tx.HistoricUSD = &usd
  268. feeUSD := usd * l2.Fee.Percentage()
  269. tx.HistoricUSD = &usd
  270. tx.L2Info.HistoricFeeUSD = &feeUSD
  271. }
  272. allTxs = append(allTxs, tx)
  273. if isUsrTx(tx) {
  274. usrTxs = append(usrTxs, tx)
  275. }
  276. }
  277. }
  278. return usrTxs, allTxs
  279. }
  280. func TestGetHistoryTxs(t *testing.T) {
  281. endpoint := apiURL + "transactions-history"
  282. fetchedTxs := []testTx{}
  283. appendIter := func(intr interface{}) {
  284. for i := 0; i < len(intr.(*testTxsResponse).Txs); i++ {
  285. tmp, err := copystructure.Copy(intr.(*testTxsResponse).Txs[i])
  286. if err != nil {
  287. panic(err)
  288. }
  289. fetchedTxs = append(fetchedTxs, tmp.(testTx))
  290. }
  291. }
  292. // Get all (no filters)
  293. limit := 8
  294. path := fmt.Sprintf("%s?limit=%d&fromItem=", endpoint, limit)
  295. err := doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter)
  296. assert.NoError(t, err)
  297. assertTxs(t, tc.allTxs, fetchedTxs)
  298. // Uncomment once tx generation for tests is fixed
  299. // // Get by ethAddr
  300. // fetchedTxs = []testTx{}
  301. // limit = 7
  302. // path = fmt.Sprintf(
  303. // "%s?hermezEthereumAddress=%s&limit=%d&fromItem=",
  304. // endpoint, tc.usrAddr, limit,
  305. // )
  306. // err = doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter)
  307. // assert.NoError(t, err)
  308. // assertTxs(t, tc.usrTxs, fetchedTxs)
  309. // // Get by bjj
  310. // fetchedTxs = []testTx{}
  311. // limit = 6
  312. // path = fmt.Sprintf(
  313. // "%s?BJJ=%s&limit=%d&fromItem=",
  314. // endpoint, tc.usrBjj, limit,
  315. // )
  316. // err = doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter)
  317. // assert.NoError(t, err)
  318. // assertTxs(t, tc.usrTxs, fetchedTxs)
  319. // Get by tokenID
  320. fetchedTxs = []testTx{}
  321. limit = 5
  322. tokenID := tc.allTxs[0].Token.TokenID
  323. path = fmt.Sprintf(
  324. "%s?tokenId=%d&limit=%d&fromItem=",
  325. endpoint, tokenID, limit,
  326. )
  327. err = doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter)
  328. assert.NoError(t, err)
  329. tokenIDTxs := []testTx{}
  330. for i := 0; i < len(tc.allTxs); i++ {
  331. if tc.allTxs[i].Token.TokenID == tokenID {
  332. tokenIDTxs = append(tokenIDTxs, tc.allTxs[i])
  333. }
  334. }
  335. assertTxs(t, tokenIDTxs, fetchedTxs)
  336. // // idx
  337. // fetchedTxs = []testTx{}
  338. // limit = 4
  339. idx := tc.allTxs[0].ToIdx
  340. // path = fmt.Sprintf(
  341. // "%s?accountIndex=%s&limit=%d&fromItem=",
  342. // endpoint, idx, limit,
  343. // )
  344. // err = doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter)
  345. // assert.NoError(t, err)
  346. // idxTxs := []testTx{}
  347. // for i := 0; i < len(tc.allTxs); i++ {
  348. // if (tc.allTxs[i].FromIdx != nil && (*tc.allTxs[i].FromIdx)[6:] == idx[6:]) ||
  349. // tc.allTxs[i].ToIdx[6:] == idx[6:] {
  350. // idxTxs = append(idxTxs, tc.allTxs[i])
  351. // }
  352. // }
  353. // assertHistoryTxAPIs(t, idxTxs, fetchedTxs)
  354. // batchNum
  355. fetchedTxs = []testTx{}
  356. limit = 3
  357. batchNum := tc.allTxs[0].BatchNum
  358. path = fmt.Sprintf(
  359. "%s?batchNum=%d&limit=%d&fromItem=",
  360. endpoint, *batchNum, limit,
  361. )
  362. err = doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter)
  363. assert.NoError(t, err)
  364. batchNumTxs := []testTx{}
  365. for i := 0; i < len(tc.allTxs); i++ {
  366. if tc.allTxs[i].BatchNum != nil &&
  367. *tc.allTxs[i].BatchNum == *batchNum {
  368. batchNumTxs = append(batchNumTxs, tc.allTxs[i])
  369. }
  370. }
  371. assertTxs(t, batchNumTxs, fetchedTxs)
  372. // type
  373. txTypes := []common.TxType{
  374. // Uncomment once test gen is fixed
  375. // common.TxTypeExit,
  376. // common.TxTypeTransfer,
  377. // common.TxTypeDeposit,
  378. common.TxTypeCreateAccountDeposit,
  379. // common.TxTypeCreateAccountDepositTransfer,
  380. // common.TxTypeDepositTransfer,
  381. common.TxTypeForceTransfer,
  382. // common.TxTypeForceExit,
  383. // common.TxTypeTransferToEthAddr,
  384. // common.TxTypeTransferToBJJ,
  385. }
  386. for _, txType := range txTypes {
  387. fetchedTxs = []testTx{}
  388. limit = 2
  389. path = fmt.Sprintf(
  390. "%s?type=%s&limit=%d&fromItem=",
  391. endpoint, txType, limit,
  392. )
  393. err = doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter)
  394. assert.NoError(t, err)
  395. txTypeTxs := []testTx{}
  396. for i := 0; i < len(tc.allTxs); i++ {
  397. if tc.allTxs[i].Type == txType {
  398. txTypeTxs = append(txTypeTxs, tc.allTxs[i])
  399. }
  400. }
  401. assertTxs(t, txTypeTxs, fetchedTxs)
  402. }
  403. // Multiple filters
  404. fetchedTxs = []testTx{}
  405. limit = 1
  406. path = fmt.Sprintf(
  407. "%s?batchNum=%d&tokenId=%d&limit=%d&fromItem=",
  408. endpoint, *batchNum, tokenID, limit,
  409. )
  410. err = doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter)
  411. assert.NoError(t, err)
  412. mixedTxs := []testTx{}
  413. for i := 0; i < len(tc.allTxs); i++ {
  414. if tc.allTxs[i].BatchNum != nil {
  415. if *tc.allTxs[i].BatchNum == *batchNum && tc.allTxs[i].Token.TokenID == tokenID {
  416. mixedTxs = append(mixedTxs, tc.allTxs[i])
  417. }
  418. }
  419. }
  420. assertTxs(t, mixedTxs, fetchedTxs)
  421. // All, in reverse order
  422. fetchedTxs = []testTx{}
  423. limit = 5
  424. path = fmt.Sprintf("%s?limit=%d&fromItem=", endpoint, limit)
  425. err = doGoodReqPaginated(path, historydb.OrderDesc, &testTxsResponse{}, appendIter)
  426. assert.NoError(t, err)
  427. flipedTxs := []testTx{}
  428. for i := 0; i < len(tc.allTxs); i++ {
  429. flipedTxs = append(flipedTxs, tc.allTxs[len(tc.allTxs)-1-i])
  430. }
  431. assertTxs(t, flipedTxs, fetchedTxs)
  432. // 400
  433. path = fmt.Sprintf(
  434. "%s?accountIndex=%s&hermezEthereumAddress=%s",
  435. endpoint, idx, tc.usrAddr,
  436. )
  437. err = doBadReq("GET", path, nil, 400)
  438. assert.NoError(t, err)
  439. path = fmt.Sprintf("%s?tokenId=X", endpoint)
  440. err = doBadReq("GET", path, nil, 400)
  441. assert.NoError(t, err)
  442. // 404
  443. path = fmt.Sprintf("%s?batchNum=999999", endpoint)
  444. err = doBadReq("GET", path, nil, 404)
  445. assert.NoError(t, err)
  446. path = fmt.Sprintf("%s?limit=1000&fromItem=999999", endpoint)
  447. err = doBadReq("GET", path, nil, 404)
  448. assert.NoError(t, err)
  449. }
  450. func TestGetHistoryTx(t *testing.T) {
  451. // Get all txs by their ID
  452. endpoint := apiURL + "transactions-history/"
  453. fetchedTxs := []testTx{}
  454. for _, tx := range tc.allTxs {
  455. fetchedTx := testTx{}
  456. err := doGoodReq("GET", endpoint+tx.TxID.String(), nil, &fetchedTx)
  457. assert.NoError(t, err)
  458. fetchedTxs = append(fetchedTxs, fetchedTx)
  459. }
  460. assertTxs(t, tc.allTxs, fetchedTxs)
  461. // 400
  462. err := doBadReq("GET", endpoint+"0x001", nil, 400)
  463. assert.NoError(t, err)
  464. // 404
  465. err = doBadReq("GET", endpoint+"0x00000000000001e240004700", nil, 404)
  466. assert.NoError(t, err)
  467. }
  468. func assertTxs(t *testing.T, expected, actual []testTx) {
  469. require.Equal(t, len(expected), len(actual))
  470. for i := 0; i < len(actual); i++ { //nolint len(actual) won't change within the loop
  471. actual[i].ItemID = 0
  472. actual[i].Token.ItemID = 0
  473. assert.Equal(t, expected[i].Timestamp.Unix(), actual[i].Timestamp.Unix())
  474. expected[i].Timestamp = actual[i].Timestamp
  475. if expected[i].Token.USDUpdate == nil {
  476. assert.Equal(t, expected[i].Token.USDUpdate, actual[i].Token.USDUpdate)
  477. } else {
  478. assert.Equal(t, expected[i].Token.USDUpdate.Unix(), actual[i].Token.USDUpdate.Unix())
  479. expected[i].Token.USDUpdate = actual[i].Token.USDUpdate
  480. }
  481. test.AssertUSD(t, expected[i].HistoricUSD, actual[i].HistoricUSD)
  482. if expected[i].L2Info != nil {
  483. test.AssertUSD(t, expected[i].L2Info.HistoricFeeUSD, actual[i].L2Info.HistoricFeeUSD)
  484. } else {
  485. test.AssertUSD(t, expected[i].L1Info.HistoricLoadAmountUSD, actual[i].L1Info.HistoricLoadAmountUSD)
  486. }
  487. assert.Equal(t, expected[i], actual[i])
  488. }
  489. }