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.

517 lines
16 KiB

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