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.

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