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.

485 lines
15 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/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 false // 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. pendingItems = t.PendingItems
  83. lastItemID = t.Txs[len(t.Txs)-1].ItemID
  84. return pendingItems, lastItemID
  85. }
  86. func (t testTxsResponse) Len() int {
  87. return len(t.Txs)
  88. }
  89. func (t testTxsResponse) New() Pendinger { return &testTxsResponse{} }
  90. func genTestTxs(
  91. l1s []common.L1Tx,
  92. l2s []common.L2Tx,
  93. accs []common.Account,
  94. tokens []historydb.TokenWithUSD,
  95. blocks []common.Block,
  96. ) []testTx {
  97. txs := []testTx{}
  98. // common.L1Tx ==> testTx
  99. for _, l1 := range l1s {
  100. token := getTokenByID(l1.TokenID, tokens)
  101. // l1.FromEthAddr and l1.FromBJJ can't be nil
  102. fromEthAddr := string(apitypes.NewHezEthAddr(l1.FromEthAddr))
  103. fromBJJ := string(apitypes.NewHezBJJ(l1.FromBJJ))
  104. tx := testTx{
  105. IsL1: "L1",
  106. TxID: l1.TxID,
  107. Type: l1.Type,
  108. Position: l1.Position,
  109. FromEthAddr: &fromEthAddr,
  110. FromBJJ: &fromBJJ,
  111. ToIdx: idxToHez(l1.ToIdx, token.Symbol),
  112. Amount: l1.Amount.String(),
  113. BatchNum: l1.BatchNum,
  114. Timestamp: getTimestamp(l1.EthBlockNum, blocks),
  115. L1Info: &testL1Info{
  116. ToForgeL1TxsNum: l1.ToForgeL1TxsNum,
  117. UserOrigin: l1.UserOrigin,
  118. DepositAmount: l1.DepositAmount.String(),
  119. AmountSuccess: true,
  120. DepositAmountSuccess: true,
  121. EthBlockNum: l1.EthBlockNum,
  122. },
  123. Token: token,
  124. }
  125. // set BatchNum for user txs
  126. if tx.L1Info.ToForgeL1TxsNum != nil {
  127. // WARNING: this is an asumption, and the test input data can brake it easily
  128. bn := common.BatchNum(*tx.L1Info.ToForgeL1TxsNum + 2)
  129. tx.BatchNum = &bn
  130. }
  131. // If FromIdx is not nil
  132. idxStr := idxToHez(l1.FromIdx, token.Symbol)
  133. tx.FromIdx = &idxStr
  134. // If tx has a normal ToIdx (>255), set FromEthAddr and FromBJJ
  135. if l1.ToIdx >= common.UserThreshold {
  136. // find account
  137. for _, acc := range accs {
  138. if l1.ToIdx == acc.Idx {
  139. toEthAddr := string(apitypes.NewHezEthAddr(acc.EthAddr))
  140. tx.ToEthAddr = &toEthAddr
  141. toBJJ := string(apitypes.NewHezBJJ(acc.PublicKey))
  142. tx.ToBJJ = &toBJJ
  143. break
  144. }
  145. }
  146. }
  147. // If the token has USD value setted
  148. if token.USD != nil {
  149. af := new(big.Float).SetInt(l1.Amount)
  150. amountFloat, _ := af.Float64()
  151. usd := *token.USD * amountFloat / math.Pow(10, float64(token.Decimals))
  152. if usd != 0 {
  153. tx.HistoricUSD = &usd
  154. }
  155. laf := new(big.Float).SetInt(l1.DepositAmount)
  156. depositAmountFloat, _ := laf.Float64()
  157. depositUSD := *token.USD * depositAmountFloat / math.Pow(10, float64(token.Decimals))
  158. if depositAmountFloat != 0 {
  159. tx.L1Info.HistoricDepositAmountUSD = &depositUSD
  160. }
  161. }
  162. txs = append(txs, tx)
  163. }
  164. // common.L2Tx ==> testTx
  165. for i := 0; i < len(l2s); i++ {
  166. token := getTokenByIdx(l2s[i].FromIdx, tokens, accs)
  167. // l1.FromIdx can't be nil
  168. fromIdx := idxToHez(l2s[i].FromIdx, token.Symbol)
  169. tx := testTx{
  170. IsL1: "L2",
  171. TxID: l2s[i].TxID,
  172. Type: l2s[i].Type,
  173. Position: l2s[i].Position,
  174. ToIdx: idxToHez(l2s[i].ToIdx, token.Symbol),
  175. FromIdx: &fromIdx,
  176. Amount: l2s[i].Amount.String(),
  177. BatchNum: &l2s[i].BatchNum,
  178. Timestamp: getTimestamp(l2s[i].EthBlockNum, blocks),
  179. L2Info: &testL2Info{
  180. Nonce: l2s[i].Nonce,
  181. Fee: l2s[i].Fee,
  182. },
  183. Token: token,
  184. }
  185. // If FromIdx is not nil
  186. if l2s[i].FromIdx != 0 {
  187. idxStr := idxToHez(l2s[i].FromIdx, token.Symbol)
  188. tx.FromIdx = &idxStr
  189. }
  190. // Set FromEthAddr and FromBJJ (FromIdx it's always >255)
  191. for _, acc := range accs {
  192. if l2s[i].FromIdx == acc.Idx {
  193. fromEthAddr := string(apitypes.NewHezEthAddr(acc.EthAddr))
  194. tx.FromEthAddr = &fromEthAddr
  195. fromBJJ := string(apitypes.NewHezBJJ(acc.PublicKey))
  196. tx.FromBJJ = &fromBJJ
  197. break
  198. }
  199. }
  200. // If tx has a normal ToIdx (>255), set FromEthAddr and FromBJJ
  201. if l2s[i].ToIdx >= common.UserThreshold {
  202. // find account
  203. for _, acc := range accs {
  204. if l2s[i].ToIdx == acc.Idx {
  205. toEthAddr := string(apitypes.NewHezEthAddr(acc.EthAddr))
  206. tx.ToEthAddr = &toEthAddr
  207. toBJJ := string(apitypes.NewHezBJJ(acc.PublicKey))
  208. tx.ToBJJ = &toBJJ
  209. break
  210. }
  211. }
  212. }
  213. // If the token has USD value setted
  214. if token.USD != nil {
  215. af := new(big.Float).SetInt(l2s[i].Amount)
  216. amountFloat, _ := af.Float64()
  217. usd := *token.USD * amountFloat / math.Pow(10, float64(token.Decimals))
  218. if usd != 0 {
  219. tx.HistoricUSD = &usd
  220. feeUSD := usd * l2s[i].Fee.Percentage()
  221. if feeUSD != 0 {
  222. tx.L2Info.HistoricFeeUSD = &feeUSD
  223. }
  224. }
  225. }
  226. txs = append(txs, tx)
  227. }
  228. // Sort txs
  229. sortedTxs := txsSort(txs)
  230. sort.Sort(sortedTxs)
  231. return []testTx(sortedTxs)
  232. }
  233. func TestGetHistoryTxs(t *testing.T) {
  234. endpoint := apiURL + "transactions-history"
  235. fetchedTxs := []testTx{}
  236. appendIter := func(intr interface{}) {
  237. for i := 0; i < len(intr.(*testTxsResponse).Txs); i++ {
  238. tmp, err := copystructure.Copy(intr.(*testTxsResponse).Txs[i])
  239. if err != nil {
  240. panic(err)
  241. }
  242. fetchedTxs = append(fetchedTxs, tmp.(testTx))
  243. }
  244. }
  245. // Get all (no filters)
  246. limit := 20
  247. path := fmt.Sprintf("%s?limit=%d", endpoint, limit)
  248. err := doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter)
  249. assert.NoError(t, err)
  250. assertTxs(t, tc.txs, fetchedTxs)
  251. // Get by ethAddr
  252. account := tc.accounts[2]
  253. fetchedTxs = []testTx{}
  254. limit = 7
  255. path = fmt.Sprintf(
  256. "%s?hezEthereumAddress=%s&limit=%d",
  257. endpoint, account.EthAddr, limit,
  258. )
  259. err = doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter)
  260. assert.NoError(t, err)
  261. accountTxs := []testTx{}
  262. for i := 0; i < len(tc.txs); i++ {
  263. tx := tc.txs[i]
  264. if (tx.FromIdx != nil && *tx.FromIdx == string(account.Idx)) ||
  265. tx.ToIdx == string(account.Idx) ||
  266. (tx.FromEthAddr != nil && *tx.FromEthAddr == string(account.EthAddr)) ||
  267. (tx.ToEthAddr != nil && *tx.ToEthAddr == string(account.EthAddr)) ||
  268. (tx.FromBJJ != nil && *tx.FromBJJ == string(account.PublicKey)) ||
  269. (tx.ToBJJ != nil && *tx.ToBJJ == string(account.PublicKey)) {
  270. accountTxs = append(accountTxs, tx)
  271. }
  272. }
  273. assertTxs(t, accountTxs, fetchedTxs)
  274. // Get by bjj
  275. fetchedTxs = []testTx{}
  276. limit = 6
  277. path = fmt.Sprintf(
  278. "%s?BJJ=%s&limit=%d",
  279. endpoint, account.PublicKey, limit,
  280. )
  281. err = doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter)
  282. assert.NoError(t, err)
  283. assertTxs(t, accountTxs, fetchedTxs)
  284. // Get by tokenID
  285. fetchedTxs = []testTx{}
  286. limit = 5
  287. tokenID := tc.txs[0].Token.TokenID
  288. path = fmt.Sprintf(
  289. "%s?tokenId=%d&limit=%d",
  290. endpoint, tokenID, limit,
  291. )
  292. err = doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter)
  293. assert.NoError(t, err)
  294. tokenIDTxs := []testTx{}
  295. for i := 0; i < len(tc.txs); i++ {
  296. if tc.txs[i].Token.TokenID == tokenID {
  297. tokenIDTxs = append(tokenIDTxs, tc.txs[i])
  298. }
  299. }
  300. assertTxs(t, tokenIDTxs, fetchedTxs)
  301. // idx
  302. fetchedTxs = []testTx{}
  303. limit = 4
  304. idxStr := tc.txs[0].ToIdx
  305. idx, err := stringToIdx(idxStr, "")
  306. assert.NoError(t, err)
  307. path = fmt.Sprintf(
  308. "%s?accountIndex=%s&limit=%d",
  309. endpoint, idxStr, limit,
  310. )
  311. err = doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter)
  312. assert.NoError(t, err)
  313. idxTxs := []testTx{}
  314. for i := 0; i < len(tc.txs); i++ {
  315. var fromIdx *common.Idx
  316. if tc.txs[i].FromIdx != nil {
  317. fromIdx, err = stringToIdx(*tc.txs[i].FromIdx, "")
  318. assert.NoError(t, err)
  319. if *fromIdx == *idx {
  320. idxTxs = append(idxTxs, tc.txs[i])
  321. continue
  322. }
  323. }
  324. toIdx, err := stringToIdx((tc.txs[i].ToIdx), "")
  325. assert.NoError(t, err)
  326. if *toIdx == *idx {
  327. idxTxs = append(idxTxs, tc.txs[i])
  328. }
  329. }
  330. assertTxs(t, idxTxs, fetchedTxs)
  331. // batchNum
  332. fetchedTxs = []testTx{}
  333. limit = 3
  334. batchNum := tc.txs[0].BatchNum
  335. path = fmt.Sprintf(
  336. "%s?batchNum=%d&limit=%d",
  337. endpoint, *batchNum, limit,
  338. )
  339. err = doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter)
  340. assert.NoError(t, err)
  341. batchNumTxs := []testTx{}
  342. for i := 0; i < len(tc.txs); i++ {
  343. if tc.txs[i].BatchNum != nil &&
  344. *tc.txs[i].BatchNum == *batchNum {
  345. batchNumTxs = append(batchNumTxs, tc.txs[i])
  346. }
  347. }
  348. assertTxs(t, batchNumTxs, fetchedTxs)
  349. // type
  350. txTypes := []common.TxType{
  351. // Uncomment once test gen is fixed
  352. common.TxTypeExit,
  353. common.TxTypeTransfer,
  354. common.TxTypeDeposit,
  355. common.TxTypeCreateAccountDeposit,
  356. common.TxTypeCreateAccountDepositTransfer,
  357. common.TxTypeDepositTransfer,
  358. common.TxTypeForceTransfer,
  359. common.TxTypeForceExit,
  360. }
  361. for _, txType := range txTypes {
  362. fetchedTxs = []testTx{}
  363. limit = 2
  364. path = fmt.Sprintf(
  365. "%s?type=%s&limit=%d",
  366. endpoint, txType, limit,
  367. )
  368. err = doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter)
  369. assert.NoError(t, err)
  370. txTypeTxs := []testTx{}
  371. for i := 0; i < len(tc.txs); i++ {
  372. if tc.txs[i].Type == txType {
  373. txTypeTxs = append(txTypeTxs, tc.txs[i])
  374. }
  375. }
  376. assertTxs(t, txTypeTxs, fetchedTxs)
  377. }
  378. // Multiple filters
  379. fetchedTxs = []testTx{}
  380. limit = 1
  381. path = fmt.Sprintf(
  382. "%s?batchNum=%d&tokenId=%d&limit=%d",
  383. endpoint, *batchNum, tokenID, limit,
  384. )
  385. err = doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter)
  386. assert.NoError(t, err)
  387. mixedTxs := []testTx{}
  388. for i := 0; i < len(tc.txs); i++ {
  389. if tc.txs[i].BatchNum != nil {
  390. if *tc.txs[i].BatchNum == *batchNum && tc.txs[i].Token.TokenID == tokenID {
  391. mixedTxs = append(mixedTxs, tc.txs[i])
  392. }
  393. }
  394. }
  395. assertTxs(t, mixedTxs, fetchedTxs)
  396. // All, in reverse order
  397. fetchedTxs = []testTx{}
  398. limit = 5
  399. path = fmt.Sprintf("%s?limit=%d", endpoint, limit)
  400. err = doGoodReqPaginated(path, historydb.OrderDesc, &testTxsResponse{}, appendIter)
  401. assert.NoError(t, err)
  402. flipedTxs := []testTx{}
  403. for i := 0; i < len(tc.txs); i++ {
  404. flipedTxs = append(flipedTxs, tc.txs[len(tc.txs)-1-i])
  405. }
  406. assertTxs(t, flipedTxs, fetchedTxs)
  407. // 400
  408. path = fmt.Sprintf(
  409. "%s?accountIndex=%s&hezEthereumAddress=%s",
  410. endpoint, idx, account.EthAddr,
  411. )
  412. err = doBadReq("GET", path, nil, 400)
  413. assert.NoError(t, err)
  414. path = fmt.Sprintf("%s?tokenId=X", endpoint)
  415. err = doBadReq("GET", path, nil, 400)
  416. assert.NoError(t, err)
  417. // 404
  418. path = fmt.Sprintf("%s?batchNum=999999", endpoint)
  419. err = doBadReq("GET", path, nil, 404)
  420. assert.NoError(t, err)
  421. path = fmt.Sprintf("%s?fromItem=1000999999", endpoint)
  422. err = doBadReq("GET", path, nil, 404)
  423. assert.NoError(t, err)
  424. }
  425. func TestGetHistoryTx(t *testing.T) {
  426. // Get all txs by their ID
  427. endpoint := apiURL + "transactions-history/"
  428. fetchedTxs := []testTx{}
  429. for _, tx := range tc.txs {
  430. fetchedTx := testTx{}
  431. err := doGoodReq("GET", endpoint+tx.TxID.String(), nil, &fetchedTx)
  432. assert.NoError(t, err)
  433. fetchedTxs = append(fetchedTxs, fetchedTx)
  434. }
  435. assertTxs(t, tc.txs, fetchedTxs)
  436. // 400
  437. err := doBadReq("GET", endpoint+"0x001", nil, 400)
  438. assert.NoError(t, err)
  439. // 404
  440. err = doBadReq("GET", endpoint+"0x00000000000001e240004700", nil, 404)
  441. assert.NoError(t, err)
  442. }
  443. func assertTxs(t *testing.T, expected, actual []testTx) {
  444. require.Equal(t, len(expected), len(actual))
  445. for i := 0; i < len(actual); i++ { //nolint len(actual) won't change within the loop
  446. assert.Equal(t, expected[i].BatchNum, actual[i].BatchNum)
  447. assert.Equal(t, expected[i].Position, actual[i].Position)
  448. actual[i].ItemID = 0
  449. actual[i].Token.ItemID = 0
  450. assert.Equal(t, expected[i].Timestamp.Unix(), actual[i].Timestamp.Unix())
  451. expected[i].Timestamp = actual[i].Timestamp
  452. if expected[i].Token.USDUpdate == nil {
  453. assert.Equal(t, expected[i].Token.USDUpdate, actual[i].Token.USDUpdate)
  454. expected[i].Token.USDUpdate = actual[i].Token.USDUpdate
  455. } else {
  456. assert.Equal(t, expected[i].Token.USDUpdate.Unix(), actual[i].Token.USDUpdate.Unix())
  457. expected[i].Token.USDUpdate = actual[i].Token.USDUpdate
  458. }
  459. test.AssertUSD(t, expected[i].HistoricUSD, actual[i].HistoricUSD)
  460. if expected[i].L2Info != nil {
  461. test.AssertUSD(t, expected[i].L2Info.HistoricFeeUSD, actual[i].L2Info.HistoricFeeUSD)
  462. } else {
  463. test.AssertUSD(t, expected[i].L1Info.HistoricDepositAmountUSD, actual[i].L1Info.HistoricDepositAmountUSD)
  464. }
  465. assert.Equal(t, expected[i], actual[i])
  466. }
  467. }