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.

1126 lines
32 KiB

  1. package api
  2. import (
  3. "context"
  4. "encoding/json"
  5. "errors"
  6. "fmt"
  7. "io"
  8. "io/ioutil"
  9. "math"
  10. "math/big"
  11. "net/http"
  12. "os"
  13. "sort"
  14. "strconv"
  15. "testing"
  16. "time"
  17. ethCommon "github.com/ethereum/go-ethereum/common"
  18. swagger "github.com/getkin/kin-openapi/openapi3filter"
  19. "github.com/gin-gonic/gin"
  20. "github.com/hermeznetwork/hermez-node/common"
  21. "github.com/hermeznetwork/hermez-node/db"
  22. "github.com/hermeznetwork/hermez-node/db/historydb"
  23. "github.com/hermeznetwork/hermez-node/db/l2db"
  24. "github.com/hermeznetwork/hermez-node/db/statedb"
  25. "github.com/hermeznetwork/hermez-node/log"
  26. "github.com/hermeznetwork/hermez-node/test"
  27. "github.com/iden3/go-iden3-crypto/babyjub"
  28. "github.com/mitchellh/copystructure"
  29. "github.com/stretchr/testify/assert"
  30. "github.com/stretchr/testify/require"
  31. )
  32. const apiPort = ":4010"
  33. const apiURL = "http://localhost" + apiPort + "/"
  34. type testCommon struct {
  35. blocks []common.Block
  36. tokens []tokenAPI
  37. batches []common.Batch
  38. usrAddr string
  39. usrBjj string
  40. accs []common.Account
  41. usrTxs []historyTxAPI
  42. allTxs []historyTxAPI
  43. exits []exitAPI
  44. usrExits []exitAPI
  45. router *swagger.Router
  46. }
  47. // TxSortFields represents the fields needed to sort L1 and L2 transactions
  48. type txSortFields struct {
  49. BatchNum *common.BatchNum
  50. Position int
  51. }
  52. // TxSortFielder is a interface that allows sorting L1 and L2 transactions in a combined way
  53. type txSortFielder interface {
  54. SortFields() txSortFields
  55. L1() *common.L1Tx
  56. L2() *common.L2Tx
  57. }
  58. // TxsSort array of TxSortFielder
  59. type txsSort []txSortFielder
  60. func (t txsSort) Len() int { return len(t) }
  61. func (t txsSort) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
  62. func (t txsSort) Less(i, j int) bool {
  63. // i not forged yet
  64. isf := t[i].SortFields()
  65. jsf := t[j].SortFields()
  66. if isf.BatchNum == nil {
  67. if jsf.BatchNum != nil { // j is already forged
  68. return false
  69. }
  70. // Both aren't forged, is i in a smaller position?
  71. return isf.Position < jsf.Position
  72. }
  73. // i is forged
  74. if jsf.BatchNum == nil {
  75. return false // j is not forged
  76. }
  77. // Both are forged
  78. if *isf.BatchNum == *jsf.BatchNum {
  79. // At the same batch, is i in a smaller position?
  80. return isf.Position < jsf.Position
  81. }
  82. // At different batches, is i in a smaller batch?
  83. return *isf.BatchNum < *jsf.BatchNum
  84. }
  85. type wrappedL1 common.L1Tx
  86. // SortFields implements TxSortFielder
  87. func (tx *wrappedL1) SortFields() txSortFields {
  88. return txSortFields{
  89. BatchNum: tx.BatchNum,
  90. Position: tx.Position,
  91. }
  92. }
  93. // L1 implements TxSortFielder
  94. func (tx *wrappedL1) L1() *common.L1Tx {
  95. l1tx := common.L1Tx(*tx)
  96. return &l1tx
  97. }
  98. // L2 implements TxSortFielder
  99. func (tx *wrappedL1) L2() *common.L2Tx { return nil }
  100. type wrappedL2 common.L2Tx
  101. // SortFields implements TxSortFielder
  102. func (tx *wrappedL2) SortFields() txSortFields {
  103. return txSortFields{
  104. BatchNum: &tx.BatchNum,
  105. Position: tx.Position,
  106. }
  107. }
  108. // L1 implements TxSortFielder
  109. func (tx *wrappedL2) L1() *common.L1Tx { return nil }
  110. // L2 implements TxSortFielder
  111. func (tx *wrappedL2) L2() *common.L2Tx {
  112. l2tx := common.L2Tx(*tx)
  113. return &l2tx
  114. }
  115. var tc testCommon
  116. func TestMain(m *testing.M) {
  117. // Init swagger
  118. router := swagger.NewRouter().WithSwaggerFromFile("./swagger.yml")
  119. // Init DBs
  120. // HistoryDB
  121. pass := os.Getenv("POSTGRES_PASS")
  122. database, err := db.InitSQLDB(5432, "localhost", "hermez", pass, "hermez")
  123. if err != nil {
  124. panic(err)
  125. }
  126. hdb := historydb.NewHistoryDB(database)
  127. err = hdb.Reorg(-1)
  128. if err != nil {
  129. panic(err)
  130. }
  131. // StateDB
  132. dir, err := ioutil.TempDir("", "tmpdb")
  133. if err != nil {
  134. panic(err)
  135. }
  136. sdb, err := statedb.NewStateDB(dir, statedb.TypeTxSelector, 0)
  137. if err != nil {
  138. panic(err)
  139. }
  140. // L2DB
  141. l2DB := l2db.NewL2DB(database, 10, 100, 24*time.Hour)
  142. test.CleanL2DB(l2DB.DB())
  143. // Init API
  144. api := gin.Default()
  145. if err := SetAPIEndpoints(
  146. true,
  147. true,
  148. api,
  149. hdb,
  150. sdb,
  151. l2DB,
  152. ); err != nil {
  153. panic(err)
  154. }
  155. // Start server
  156. server := &http.Server{Addr: apiPort, Handler: api}
  157. go func() {
  158. if err := server.ListenAndServe(); err != nil &&
  159. err != http.ErrServerClosed {
  160. panic(err)
  161. }
  162. }()
  163. // Populate DBs
  164. // Clean DB
  165. err = h.Reorg(0)
  166. if err != nil {
  167. panic(err)
  168. }
  169. // Gen blocks and add them to DB
  170. const nBlocks = 5
  171. blocks := test.GenBlocks(1, nBlocks+1)
  172. err = h.AddBlocks(blocks)
  173. if err != nil {
  174. panic(err)
  175. }
  176. // Gen tokens and add them to DB
  177. const nTokens = 10
  178. tokens := test.GenTokens(nTokens, blocks)
  179. err = h.AddTokens(tokens)
  180. if err != nil {
  181. panic(err)
  182. }
  183. // Set token value
  184. tokensUSD := []tokenAPI{}
  185. for i, tkn := range tokens {
  186. token := tokenAPI{
  187. TokenID: tkn.TokenID,
  188. EthBlockNum: tkn.EthBlockNum,
  189. EthAddr: tkn.EthAddr,
  190. Name: tkn.Name,
  191. Symbol: tkn.Symbol,
  192. Decimals: tkn.Decimals,
  193. }
  194. // Set value of 50% of the tokens
  195. if i%2 != 0 {
  196. value := float64(i) * 1.234567
  197. now := time.Now().UTC()
  198. token.USD = &value
  199. token.USDUpdate = &now
  200. err = h.UpdateTokenValue(token.Symbol, value)
  201. if err != nil {
  202. panic(err)
  203. }
  204. }
  205. tokensUSD = append(tokensUSD, token)
  206. }
  207. // Gen batches and add them to DB
  208. const nBatches = 10
  209. batches := test.GenBatches(nBatches, blocks)
  210. err = h.AddBatches(batches)
  211. if err != nil {
  212. panic(err)
  213. }
  214. // Gen accounts and add them to DB
  215. const totalAccounts = 40
  216. const userAccounts = 4
  217. usrAddr := ethCommon.BigToAddress(big.NewInt(4896847))
  218. privK := babyjub.NewRandPrivKey()
  219. usrBjj := privK.Public()
  220. accs := test.GenAccounts(totalAccounts, userAccounts, tokens, &usrAddr, usrBjj, batches)
  221. err = h.AddAccounts(accs)
  222. if err != nil {
  223. panic(err)
  224. }
  225. // Gen exits and add them to DB
  226. const totalExits = 40
  227. exits := test.GenExitTree(totalExits, batches, accs)
  228. err = h.AddExitTree(exits)
  229. if err != nil {
  230. panic(err)
  231. }
  232. // Gen L1Txs and add them to DB
  233. const totalL1Txs = 40
  234. const userL1Txs = 4
  235. usrL1Txs, othrL1Txs := test.GenL1Txs(256, totalL1Txs, userL1Txs, &usrAddr, accs, tokens, blocks, batches)
  236. // Gen L2Txs and add them to DB
  237. const totalL2Txs = 20
  238. const userL2Txs = 4
  239. usrL2Txs, othrL2Txs := test.GenL2Txs(256+totalL1Txs, totalL2Txs, userL2Txs, &usrAddr, accs, tokens, blocks, batches)
  240. // Order txs
  241. sortedTxs := []txSortFielder{}
  242. for i := 0; i < len(usrL1Txs); i++ {
  243. wL1 := wrappedL1(usrL1Txs[i])
  244. sortedTxs = append(sortedTxs, &wL1)
  245. }
  246. for i := 0; i < len(othrL1Txs); i++ {
  247. wL1 := wrappedL1(othrL1Txs[i])
  248. sortedTxs = append(sortedTxs, &wL1)
  249. }
  250. for i := 0; i < len(usrL2Txs); i++ {
  251. wL2 := wrappedL2(usrL2Txs[i])
  252. sortedTxs = append(sortedTxs, &wL2)
  253. }
  254. for i := 0; i < len(othrL2Txs); i++ {
  255. wL2 := wrappedL2(othrL2Txs[i])
  256. sortedTxs = append(sortedTxs, &wL2)
  257. }
  258. sort.Sort(txsSort(sortedTxs))
  259. // Add txs to DB and prepare them for test commons
  260. usrTxs := []historyTxAPI{}
  261. allTxs := []historyTxAPI{}
  262. getTimestamp := func(blockNum int64) time.Time {
  263. for i := 0; i < len(blocks); i++ {
  264. if blocks[i].EthBlockNum == blockNum {
  265. return blocks[i].Timestamp
  266. }
  267. }
  268. panic("timesamp not found")
  269. }
  270. getToken := func(id common.TokenID) tokenAPI {
  271. for i := 0; i < len(tokensUSD); i++ {
  272. if tokensUSD[i].TokenID == id {
  273. return tokensUSD[i]
  274. }
  275. }
  276. panic("token not found")
  277. }
  278. getTokenByIdx := func(idx common.Idx) tokenAPI {
  279. for _, acc := range accs {
  280. if idx == acc.Idx {
  281. return getToken(acc.TokenID)
  282. }
  283. }
  284. panic("token not found")
  285. }
  286. usrIdxs := []string{}
  287. for _, acc := range accs {
  288. if acc.EthAddr == usrAddr || acc.PublicKey == usrBjj {
  289. for _, token := range tokens {
  290. if token.TokenID == acc.TokenID {
  291. usrIdxs = append(usrIdxs, idxToHez(acc.Idx, token.Symbol))
  292. }
  293. }
  294. }
  295. }
  296. isUsrTx := func(tx historyTxAPI) bool {
  297. for _, idx := range usrIdxs {
  298. if tx.FromIdx != nil && *tx.FromIdx == idx {
  299. return true
  300. }
  301. if tx.ToIdx == idx {
  302. return true
  303. }
  304. }
  305. return false
  306. }
  307. for _, genericTx := range sortedTxs {
  308. l1 := genericTx.L1()
  309. l2 := genericTx.L2()
  310. if l1 != nil {
  311. // Add L1 tx to DB
  312. err = h.AddL1Txs([]common.L1Tx{*l1})
  313. if err != nil {
  314. panic(err)
  315. }
  316. // L1Tx ==> historyTxAPI
  317. token := getToken(l1.TokenID)
  318. tx := historyTxAPI{
  319. IsL1: "L1",
  320. TxID: l1.TxID.String(),
  321. Type: l1.Type,
  322. Position: l1.Position,
  323. ToIdx: idxToHez(l1.ToIdx, token.Symbol),
  324. Amount: l1.Amount.String(),
  325. BatchNum: l1.BatchNum,
  326. Timestamp: getTimestamp(l1.EthBlockNum),
  327. L1Info: &l1Info{
  328. ToForgeL1TxsNum: l1.ToForgeL1TxsNum,
  329. UserOrigin: l1.UserOrigin,
  330. FromEthAddr: ethAddrToHez(l1.FromEthAddr),
  331. FromBJJ: bjjToString(l1.FromBJJ),
  332. LoadAmount: l1.LoadAmount.String(),
  333. EthBlockNum: l1.EthBlockNum,
  334. },
  335. Token: token,
  336. }
  337. if l1.FromIdx != 0 {
  338. idxStr := idxToHez(l1.FromIdx, token.Symbol)
  339. tx.FromIdx = &idxStr
  340. }
  341. if token.USD != nil {
  342. af := new(big.Float).SetInt(l1.Amount)
  343. amountFloat, _ := af.Float64()
  344. usd := *token.USD * amountFloat / math.Pow(10, float64(token.Decimals))
  345. tx.HistoricUSD = &usd
  346. laf := new(big.Float).SetInt(l1.LoadAmount)
  347. loadAmountFloat, _ := laf.Float64()
  348. loadUSD := *token.USD * loadAmountFloat / math.Pow(10, float64(token.Decimals))
  349. tx.L1Info.HistoricLoadAmountUSD = &loadUSD
  350. }
  351. allTxs = append(allTxs, tx)
  352. if isUsrTx(tx) {
  353. usrTxs = append(usrTxs, tx)
  354. }
  355. } else {
  356. // Add L2 tx to DB
  357. err = h.AddL2Txs([]common.L2Tx{*l2})
  358. if err != nil {
  359. panic(err)
  360. }
  361. // L2Tx ==> historyTxAPI
  362. var tokenID common.TokenID
  363. found := false
  364. for _, acc := range accs {
  365. if acc.Idx == l2.FromIdx {
  366. found = true
  367. tokenID = acc.TokenID
  368. break
  369. }
  370. }
  371. if !found {
  372. panic("tokenID not found")
  373. }
  374. token := getToken(tokenID)
  375. tx := historyTxAPI{
  376. IsL1: "L2",
  377. TxID: l2.TxID.String(),
  378. Type: l2.Type,
  379. Position: l2.Position,
  380. ToIdx: idxToHez(l2.ToIdx, token.Symbol),
  381. Amount: l2.Amount.String(),
  382. BatchNum: &l2.BatchNum,
  383. Timestamp: getTimestamp(l2.EthBlockNum),
  384. L2Info: &l2Info{
  385. Nonce: l2.Nonce,
  386. Fee: l2.Fee,
  387. },
  388. Token: token,
  389. }
  390. if l2.FromIdx != 0 {
  391. idxStr := idxToHez(l2.FromIdx, token.Symbol)
  392. tx.FromIdx = &idxStr
  393. }
  394. if token.USD != nil {
  395. af := new(big.Float).SetInt(l2.Amount)
  396. amountFloat, _ := af.Float64()
  397. usd := *token.USD * amountFloat / math.Pow(10, float64(token.Decimals))
  398. tx.HistoricUSD = &usd
  399. feeUSD := usd * l2.Fee.Percentage()
  400. tx.HistoricUSD = &usd
  401. tx.L2Info.HistoricFeeUSD = &feeUSD
  402. }
  403. allTxs = append(allTxs, tx)
  404. if isUsrTx(tx) {
  405. usrTxs = append(usrTxs, tx)
  406. }
  407. }
  408. }
  409. // Transform exits to API
  410. exitsToAPIExits := func(exits []common.ExitInfo, accs []common.Account, tokens []common.Token) []exitAPI {
  411. historyExits := []historydb.HistoryExit{}
  412. for _, exit := range exits {
  413. token := getTokenByIdx(exit.AccountIdx)
  414. historyExits = append(historyExits, historydb.HistoryExit{
  415. BatchNum: exit.BatchNum,
  416. AccountIdx: exit.AccountIdx,
  417. MerkleProof: exit.MerkleProof,
  418. Balance: exit.Balance,
  419. InstantWithdrawn: exit.InstantWithdrawn,
  420. DelayedWithdrawRequest: exit.DelayedWithdrawRequest,
  421. DelayedWithdrawn: exit.DelayedWithdrawn,
  422. TokenID: token.TokenID,
  423. TokenEthBlockNum: token.EthBlockNum,
  424. TokenEthAddr: token.EthAddr,
  425. TokenName: token.Name,
  426. TokenSymbol: token.Symbol,
  427. TokenDecimals: token.Decimals,
  428. TokenUSD: token.USD,
  429. TokenUSDUpdate: token.USDUpdate,
  430. })
  431. }
  432. return historyExitsToAPI(historyExits)
  433. }
  434. apiExits := exitsToAPIExits(exits, accs, tokens)
  435. // sort.Sort(apiExits)
  436. usrExits := []exitAPI{}
  437. for _, exit := range apiExits {
  438. for _, idx := range usrIdxs {
  439. if idx == exit.AccountIdx {
  440. usrExits = append(usrExits, exit)
  441. }
  442. }
  443. }
  444. tc = testCommon{
  445. blocks: blocks,
  446. tokens: tokensUSD,
  447. batches: batches,
  448. usrAddr: ethAddrToHez(usrAddr),
  449. usrBjj: bjjToString(usrBjj),
  450. accs: accs,
  451. usrTxs: usrTxs,
  452. allTxs: allTxs,
  453. exits: apiExits,
  454. usrExits: usrExits,
  455. router: router,
  456. }
  457. // Run tests
  458. result := m.Run()
  459. // Stop server
  460. if err := server.Shutdown(context.Background()); err != nil {
  461. panic(err)
  462. }
  463. if err := database.Close(); err != nil {
  464. panic(err)
  465. }
  466. os.Exit(result)
  467. }
  468. func TestGetHistoryTxs(t *testing.T) {
  469. endpoint := apiURL + "transactions-history"
  470. fetchedTxs := []historyTxAPI{}
  471. appendIter := func(intr interface{}) {
  472. for i := 0; i < len(intr.(*historyTxsAPI).Txs); i++ {
  473. tmp, err := copystructure.Copy(intr.(*historyTxsAPI).Txs[i])
  474. if err != nil {
  475. panic(err)
  476. }
  477. fetchedTxs = append(fetchedTxs, tmp.(historyTxAPI))
  478. }
  479. }
  480. // Get all (no filters)
  481. limit := 8
  482. path := fmt.Sprintf("%s?limit=%d&fromItem=", endpoint, limit)
  483. err := doGoodReqPaginated(path, historydb.OrderAsc, &historyTxsAPI{}, appendIter)
  484. assert.NoError(t, err)
  485. assertHistoryTxAPIs(t, tc.allTxs, fetchedTxs)
  486. // Uncomment once tx generation for tests is fixed
  487. // // Get by ethAddr
  488. // fetchedTxs = []historyTxAPI{}
  489. // limit = 7
  490. // path = fmt.Sprintf(
  491. // "%s?hermezEthereumAddress=%s&limit=%d&fromItem=",
  492. // endpoint, tc.usrAddr, limit,
  493. // )
  494. // err = doGoodReqPaginated(path, historydb.OrderAsc, &historyTxsAPI{}, appendIter)
  495. // assert.NoError(t, err)
  496. // assertHistoryTxAPIs(t, tc.usrTxs, fetchedTxs)
  497. // // Get by bjj
  498. // fetchedTxs = []historyTxAPI{}
  499. // limit = 6
  500. // path = fmt.Sprintf(
  501. // "%s?BJJ=%s&limit=%d&fromItem=",
  502. // endpoint, tc.usrBjj, limit,
  503. // )
  504. // err = doGoodReqPaginated(path, historydb.OrderAsc, &historyTxsAPI{}, appendIter)
  505. // assert.NoError(t, err)
  506. // assertHistoryTxAPIs(t, tc.usrTxs, fetchedTxs)
  507. // Get by tokenID
  508. fetchedTxs = []historyTxAPI{}
  509. limit = 5
  510. tokenID := tc.allTxs[0].Token.TokenID
  511. path = fmt.Sprintf(
  512. "%s?tokenId=%d&limit=%d&fromItem=",
  513. endpoint, tokenID, limit,
  514. )
  515. err = doGoodReqPaginated(path, historydb.OrderAsc, &historyTxsAPI{}, appendIter)
  516. assert.NoError(t, err)
  517. tokenIDTxs := []historyTxAPI{}
  518. for i := 0; i < len(tc.allTxs); i++ {
  519. if tc.allTxs[i].Token.TokenID == tokenID {
  520. tokenIDTxs = append(tokenIDTxs, tc.allTxs[i])
  521. }
  522. }
  523. assertHistoryTxAPIs(t, tokenIDTxs, fetchedTxs)
  524. // idx
  525. fetchedTxs = []historyTxAPI{}
  526. limit = 4
  527. idx := tc.allTxs[0].ToIdx
  528. path = fmt.Sprintf(
  529. "%s?accountIndex=%s&limit=%d&fromItem=",
  530. endpoint, idx, limit,
  531. )
  532. err = doGoodReqPaginated(path, historydb.OrderAsc, &historyTxsAPI{}, appendIter)
  533. assert.NoError(t, err)
  534. idxTxs := []historyTxAPI{}
  535. for i := 0; i < len(tc.allTxs); i++ {
  536. if (tc.allTxs[i].FromIdx != nil && (*tc.allTxs[i].FromIdx)[6:] == idx[6:]) ||
  537. tc.allTxs[i].ToIdx[6:] == idx[6:] {
  538. idxTxs = append(idxTxs, tc.allTxs[i])
  539. }
  540. }
  541. assertHistoryTxAPIs(t, idxTxs, fetchedTxs)
  542. // batchNum
  543. fetchedTxs = []historyTxAPI{}
  544. limit = 3
  545. batchNum := tc.allTxs[0].BatchNum
  546. path = fmt.Sprintf(
  547. "%s?batchNum=%d&limit=%d&fromItem=",
  548. endpoint, *batchNum, limit,
  549. )
  550. err = doGoodReqPaginated(path, historydb.OrderAsc, &historyTxsAPI{}, appendIter)
  551. assert.NoError(t, err)
  552. batchNumTxs := []historyTxAPI{}
  553. for i := 0; i < len(tc.allTxs); i++ {
  554. if tc.allTxs[i].BatchNum != nil &&
  555. *tc.allTxs[i].BatchNum == *batchNum {
  556. batchNumTxs = append(batchNumTxs, tc.allTxs[i])
  557. }
  558. }
  559. assertHistoryTxAPIs(t, batchNumTxs, fetchedTxs)
  560. // type
  561. txTypes := []common.TxType{
  562. // Uncomment once test gen is fixed
  563. // common.TxTypeExit,
  564. // common.TxTypeTransfer,
  565. // common.TxTypeDeposit,
  566. common.TxTypeCreateAccountDeposit,
  567. // common.TxTypeCreateAccountDepositTransfer,
  568. // common.TxTypeDepositTransfer,
  569. common.TxTypeForceTransfer,
  570. // common.TxTypeForceExit,
  571. // common.TxTypeTransferToEthAddr,
  572. // common.TxTypeTransferToBJJ,
  573. }
  574. for _, txType := range txTypes {
  575. fetchedTxs = []historyTxAPI{}
  576. limit = 2
  577. path = fmt.Sprintf(
  578. "%s?type=%s&limit=%d&fromItem=",
  579. endpoint, txType, limit,
  580. )
  581. err = doGoodReqPaginated(path, historydb.OrderAsc, &historyTxsAPI{}, appendIter)
  582. assert.NoError(t, err)
  583. txTypeTxs := []historyTxAPI{}
  584. for i := 0; i < len(tc.allTxs); i++ {
  585. if tc.allTxs[i].Type == txType {
  586. txTypeTxs = append(txTypeTxs, tc.allTxs[i])
  587. }
  588. }
  589. assertHistoryTxAPIs(t, txTypeTxs, fetchedTxs)
  590. }
  591. // Multiple filters
  592. fetchedTxs = []historyTxAPI{}
  593. limit = 1
  594. path = fmt.Sprintf(
  595. "%s?batchNum=%d&tokenId=%d&limit=%d&fromItem=",
  596. endpoint, *batchNum, tokenID, limit,
  597. )
  598. err = doGoodReqPaginated(path, historydb.OrderAsc, &historyTxsAPI{}, appendIter)
  599. assert.NoError(t, err)
  600. mixedTxs := []historyTxAPI{}
  601. for i := 0; i < len(tc.allTxs); i++ {
  602. if tc.allTxs[i].BatchNum != nil {
  603. if *tc.allTxs[i].BatchNum == *batchNum && tc.allTxs[i].Token.TokenID == tokenID {
  604. mixedTxs = append(mixedTxs, tc.allTxs[i])
  605. }
  606. }
  607. }
  608. assertHistoryTxAPIs(t, mixedTxs, fetchedTxs)
  609. // All, in reverse order
  610. fetchedTxs = []historyTxAPI{}
  611. limit = 5
  612. path = fmt.Sprintf("%s?limit=%d&fromItem=", endpoint, limit)
  613. err = doGoodReqPaginated(path, historydb.OrderDesc, &historyTxsAPI{}, appendIter)
  614. assert.NoError(t, err)
  615. flipedTxs := []historyTxAPI{}
  616. for i := 0; i < len(tc.allTxs); i++ {
  617. flipedTxs = append(flipedTxs, tc.allTxs[len(tc.allTxs)-1-i])
  618. }
  619. assertHistoryTxAPIs(t, flipedTxs, fetchedTxs)
  620. // 400
  621. path = fmt.Sprintf(
  622. "%s?accountIndex=%s&hermezEthereumAddress=%s",
  623. endpoint, idx, tc.usrAddr,
  624. )
  625. err = doBadReq("GET", path, nil, 400)
  626. assert.NoError(t, err)
  627. path = fmt.Sprintf("%s?tokenId=X", endpoint)
  628. err = doBadReq("GET", path, nil, 400)
  629. assert.NoError(t, err)
  630. // 404
  631. path = fmt.Sprintf("%s?batchNum=999999", endpoint)
  632. err = doBadReq("GET", path, nil, 404)
  633. assert.NoError(t, err)
  634. path = fmt.Sprintf("%s?limit=1000&fromItem=999999", endpoint)
  635. err = doBadReq("GET", path, nil, 404)
  636. assert.NoError(t, err)
  637. }
  638. func TestGetHistoryTx(t *testing.T) {
  639. // Get all txs by their ID
  640. endpoint := apiURL + "transactions-history/"
  641. fetchedTxs := []historyTxAPI{}
  642. for _, tx := range tc.allTxs {
  643. fetchedTx := historyTxAPI{}
  644. assert.NoError(t, doGoodReq("GET", endpoint+tx.TxID, nil, &fetchedTx))
  645. fetchedTxs = append(fetchedTxs, fetchedTx)
  646. }
  647. assertHistoryTxAPIs(t, tc.allTxs, fetchedTxs)
  648. // 400
  649. err := doBadReq("GET", endpoint+"0x001", nil, 400)
  650. assert.NoError(t, err)
  651. // 404
  652. err = doBadReq("GET", endpoint+"0x00000000000001e240004700", nil, 404)
  653. assert.NoError(t, err)
  654. }
  655. func assertHistoryTxAPIs(t *testing.T, expected, actual []historyTxAPI) {
  656. require.Equal(t, len(expected), len(actual))
  657. for i := 0; i < len(actual); i++ { //nolint len(actual) won't change within the loop
  658. actual[i].ItemID = 0
  659. assert.Equal(t, expected[i].Timestamp.Unix(), actual[i].Timestamp.Unix())
  660. expected[i].Timestamp = actual[i].Timestamp
  661. if expected[i].Token.USDUpdate == nil {
  662. assert.Equal(t, expected[i].Token.USDUpdate, actual[i].Token.USDUpdate)
  663. } else {
  664. assert.Equal(t, expected[i].Token.USDUpdate.Unix(), actual[i].Token.USDUpdate.Unix())
  665. expected[i].Token.USDUpdate = actual[i].Token.USDUpdate
  666. }
  667. test.AssertUSD(t, expected[i].HistoricUSD, actual[i].HistoricUSD)
  668. if expected[i].L2Info != nil {
  669. test.AssertUSD(t, expected[i].L2Info.HistoricFeeUSD, actual[i].L2Info.HistoricFeeUSD)
  670. } else {
  671. test.AssertUSD(t, expected[i].L1Info.HistoricLoadAmountUSD, actual[i].L1Info.HistoricLoadAmountUSD)
  672. }
  673. assert.Equal(t, expected[i], actual[i])
  674. }
  675. }
  676. func TestGetExits(t *testing.T) {
  677. endpoint := apiURL + "exits"
  678. fetchedExits := []exitAPI{}
  679. appendIter := func(intr interface{}) {
  680. for i := 0; i < len(intr.(*exitsAPI).Exits); i++ {
  681. tmp, err := copystructure.Copy(intr.(*exitsAPI).Exits[i])
  682. if err != nil {
  683. panic(err)
  684. }
  685. fetchedExits = append(fetchedExits, tmp.(exitAPI))
  686. }
  687. }
  688. // Get all (no filters)
  689. limit := 8
  690. path := fmt.Sprintf("%s?limit=%d&fromItem=", endpoint, limit)
  691. err := doGoodReqPaginated(path, historydb.OrderAsc, &exitsAPI{}, appendIter)
  692. assert.NoError(t, err)
  693. assertExitAPIs(t, tc.exits, fetchedExits)
  694. // Get by ethAddr
  695. fetchedExits = []exitAPI{}
  696. limit = 7
  697. path = fmt.Sprintf(
  698. "%s?hermezEthereumAddress=%s&limit=%d&fromItem=",
  699. endpoint, tc.usrAddr, limit,
  700. )
  701. err = doGoodReqPaginated(path, historydb.OrderAsc, &exitsAPI{}, appendIter)
  702. assert.NoError(t, err)
  703. assertExitAPIs(t, tc.usrExits, fetchedExits)
  704. // Get by bjj
  705. fetchedExits = []exitAPI{}
  706. limit = 6
  707. path = fmt.Sprintf(
  708. "%s?BJJ=%s&limit=%d&fromItem=",
  709. endpoint, tc.usrBjj, limit,
  710. )
  711. err = doGoodReqPaginated(path, historydb.OrderAsc, &exitsAPI{}, appendIter)
  712. assert.NoError(t, err)
  713. assertExitAPIs(t, tc.usrExits, fetchedExits)
  714. // Get by tokenID
  715. fetchedExits = []exitAPI{}
  716. limit = 5
  717. tokenID := tc.exits[0].Token.TokenID
  718. path = fmt.Sprintf(
  719. "%s?tokenId=%d&limit=%d&fromItem=",
  720. endpoint, tokenID, limit,
  721. )
  722. err = doGoodReqPaginated(path, historydb.OrderAsc, &exitsAPI{}, appendIter)
  723. assert.NoError(t, err)
  724. tokenIDExits := []exitAPI{}
  725. for i := 0; i < len(tc.exits); i++ {
  726. if tc.exits[i].Token.TokenID == tokenID {
  727. tokenIDExits = append(tokenIDExits, tc.exits[i])
  728. }
  729. }
  730. assertExitAPIs(t, tokenIDExits, fetchedExits)
  731. // idx
  732. fetchedExits = []exitAPI{}
  733. limit = 4
  734. idx := tc.exits[0].AccountIdx
  735. path = fmt.Sprintf(
  736. "%s?accountIndex=%s&limit=%d&fromItem=",
  737. endpoint, idx, limit,
  738. )
  739. err = doGoodReqPaginated(path, historydb.OrderAsc, &exitsAPI{}, appendIter)
  740. assert.NoError(t, err)
  741. idxExits := []exitAPI{}
  742. for i := 0; i < len(tc.exits); i++ {
  743. if tc.exits[i].AccountIdx[6:] == idx[6:] {
  744. idxExits = append(idxExits, tc.exits[i])
  745. }
  746. }
  747. assertExitAPIs(t, idxExits, fetchedExits)
  748. // batchNum
  749. fetchedExits = []exitAPI{}
  750. limit = 3
  751. batchNum := tc.exits[0].BatchNum
  752. path = fmt.Sprintf(
  753. "%s?batchNum=%d&limit=%d&fromItem=",
  754. endpoint, batchNum, limit,
  755. )
  756. err = doGoodReqPaginated(path, historydb.OrderAsc, &exitsAPI{}, appendIter)
  757. assert.NoError(t, err)
  758. batchNumExits := []exitAPI{}
  759. for i := 0; i < len(tc.exits); i++ {
  760. if tc.exits[i].BatchNum == batchNum {
  761. batchNumExits = append(batchNumExits, tc.exits[i])
  762. }
  763. }
  764. assertExitAPIs(t, batchNumExits, fetchedExits)
  765. // Multiple filters
  766. fetchedExits = []exitAPI{}
  767. limit = 1
  768. path = fmt.Sprintf(
  769. "%s?batchNum=%d&tokeId=%d&limit=%d&fromItem=",
  770. endpoint, batchNum, tokenID, limit,
  771. )
  772. err = doGoodReqPaginated(path, historydb.OrderAsc, &exitsAPI{}, appendIter)
  773. assert.NoError(t, err)
  774. mixedExits := []exitAPI{}
  775. flipedExits := []exitAPI{}
  776. for i := 0; i < len(tc.exits); i++ {
  777. if tc.exits[i].BatchNum == batchNum && tc.exits[i].Token.TokenID == tokenID {
  778. mixedExits = append(mixedExits, tc.exits[i])
  779. }
  780. flipedExits = append(flipedExits, tc.exits[len(tc.exits)-1-i])
  781. }
  782. assertExitAPIs(t, mixedExits, fetchedExits)
  783. // All, in reverse order
  784. fetchedExits = []exitAPI{}
  785. limit = 5
  786. path = fmt.Sprintf("%s?limit=%d&fromItem=", endpoint, limit)
  787. err = doGoodReqPaginated(path, historydb.OrderDesc, &exitsAPI{}, appendIter)
  788. assert.NoError(t, err)
  789. assertExitAPIs(t, flipedExits, fetchedExits)
  790. // 400
  791. path = fmt.Sprintf(
  792. "%s?accountIndex=%s&hermezEthereumAddress=%s",
  793. endpoint, idx, tc.usrAddr,
  794. )
  795. err = doBadReq("GET", path, nil, 400)
  796. assert.NoError(t, err)
  797. path = fmt.Sprintf("%s?tokenId=X", endpoint)
  798. err = doBadReq("GET", path, nil, 400)
  799. assert.NoError(t, err)
  800. // 404
  801. path = fmt.Sprintf("%s?batchNum=999999", endpoint)
  802. err = doBadReq("GET", path, nil, 404)
  803. assert.NoError(t, err)
  804. path = fmt.Sprintf("%s?limit=1000&fromItem=1000", endpoint)
  805. err = doBadReq("GET", path, nil, 404)
  806. assert.NoError(t, err)
  807. }
  808. func TestGetExit(t *testing.T) {
  809. // Get all txs by their ID
  810. endpoint := apiURL + "exits/"
  811. fetchedExits := []exitAPI{}
  812. for _, exit := range tc.exits {
  813. fetchedExit := exitAPI{}
  814. assert.NoError(
  815. t, doGoodReq(
  816. "GET",
  817. fmt.Sprintf("%s%d/%s", endpoint, exit.BatchNum, exit.AccountIdx),
  818. nil, &fetchedExit,
  819. ),
  820. )
  821. fetchedExits = append(fetchedExits, fetchedExit)
  822. }
  823. assertExitAPIs(t, tc.exits, fetchedExits)
  824. // 400
  825. err := doBadReq("GET", endpoint+"1/haz:BOOM:1", nil, 400)
  826. assert.NoError(t, err)
  827. err = doBadReq("GET", endpoint+"-1/hez:BOOM:1", nil, 400)
  828. assert.NoError(t, err)
  829. // 404
  830. err = doBadReq("GET", endpoint+"494/hez:XXX:1", nil, 404)
  831. assert.NoError(t, err)
  832. }
  833. func assertExitAPIs(t *testing.T, expected, actual []exitAPI) {
  834. require.Equal(t, len(expected), len(actual))
  835. for i := 0; i < len(actual); i++ { //nolint len(actual) won't change within the loop
  836. actual[i].ItemID = 0
  837. if expected[i].Token.USDUpdate == nil {
  838. assert.Equal(t, expected[i].Token.USDUpdate, actual[i].Token.USDUpdate)
  839. } else {
  840. assert.Equal(t, expected[i].Token.USDUpdate.Unix(), actual[i].Token.USDUpdate.Unix())
  841. expected[i].Token.USDUpdate = actual[i].Token.USDUpdate
  842. }
  843. assert.Equal(t, expected[i], actual[i])
  844. }
  845. }
  846. func TestGetToken(t *testing.T) {
  847. // Get all txs by their ID
  848. endpoint := apiURL + "tokens/"
  849. fetchedTokens := []tokenAPI{}
  850. for _, token := range tc.tokens {
  851. fetchedToken := tokenAPI{}
  852. assert.NoError(t, doGoodReq("GET", endpoint+strconv.Itoa(int(token.TokenID)), nil, &fetchedToken))
  853. fetchedTokens = append(fetchedTokens, fetchedToken)
  854. }
  855. assertTokensAPIs(t, tc.tokens, fetchedTokens)
  856. }
  857. func TestGetTokens(t *testing.T) {
  858. endpoint := apiURL + "tokens"
  859. fetchedTokens := []tokenAPI{}
  860. appendIter := func(intr interface{}) {
  861. for i := 0; i < len(intr.(*tokensAPI).Tokens); i++ {
  862. tmp, err := copystructure.Copy(intr.(*tokensAPI).Tokens[i])
  863. if err != nil {
  864. panic(err)
  865. }
  866. fetchedTokens = append(fetchedTokens, tmp.(tokenAPI))
  867. }
  868. }
  869. // Get all (no filters)
  870. limit := 8
  871. path := fmt.Sprintf("%s?limit=%d&fromItem=", endpoint, limit)
  872. err := doGoodReqPaginated(path, historydb.OrderAsc, &tokensAPI{}, appendIter)
  873. assert.NoError(t, err)
  874. assertTokensAPIs(t, tc.tokens, fetchedTokens)
  875. // Get by tokenIds
  876. fetchedTokens = []tokenAPI{}
  877. limit = 7
  878. stringIds := strconv.Itoa(int(tc.tokens[2].TokenID)) + "," + strconv.Itoa(int(tc.tokens[5].TokenID)) + "," + strconv.Itoa(int(tc.tokens[6].TokenID))
  879. path = fmt.Sprintf(
  880. "%s?ids=%s&limit=%d&fromItem=",
  881. endpoint, stringIds, limit,
  882. )
  883. err = doGoodReqPaginated(path, historydb.OrderAsc, &tokensAPI{}, appendIter)
  884. assert.NoError(t, err)
  885. var tokensFiltered []tokenAPI
  886. tokensFiltered = append(tokensFiltered, tc.tokens[2])
  887. tokensFiltered = append(tokensFiltered, tc.tokens[5])
  888. tokensFiltered = append(tokensFiltered, tc.tokens[6])
  889. assertTokensAPIs(t, tokensFiltered, fetchedTokens)
  890. // Get by symbols
  891. fetchedTokens = []tokenAPI{}
  892. limit = 7
  893. stringSymbols := tc.tokens[1].Symbol + "," + tc.tokens[3].Symbol
  894. path = fmt.Sprintf(
  895. "%s?symbols=%s&limit=%d&fromItem=",
  896. endpoint, stringSymbols, limit,
  897. )
  898. err = doGoodReqPaginated(path, historydb.OrderAsc, &tokensAPI{}, appendIter)
  899. assert.NoError(t, err)
  900. tokensFiltered = nil
  901. tokensFiltered = append(tokensFiltered, tc.tokens[1])
  902. tokensFiltered = append(tokensFiltered, tc.tokens[3])
  903. assertTokensAPIs(t, tokensFiltered, fetchedTokens)
  904. // Get by name
  905. fetchedTokens = []tokenAPI{}
  906. limit = 5
  907. stringName := tc.tokens[8].Name[4:5]
  908. path = fmt.Sprintf(
  909. "%s?name=%s&limit=%d&fromItem=",
  910. endpoint, stringName, limit,
  911. )
  912. err = doGoodReqPaginated(path, historydb.OrderAsc, &tokensAPI{}, appendIter)
  913. assert.NoError(t, err)
  914. tokensFiltered = nil
  915. tokensFiltered = append(tokensFiltered, tc.tokens[8])
  916. assertTokensAPIs(t, tokensFiltered, fetchedTokens)
  917. // Multiple filters
  918. fetchedTokens = []tokenAPI{}
  919. limit = 5
  920. stringSymbols = tc.tokens[2].Symbol + "," + tc.tokens[6].Symbol
  921. stringIds = strconv.Itoa(int(tc.tokens[2].TokenID)) + "," + strconv.Itoa(int(tc.tokens[5].TokenID)) + "," + strconv.Itoa(int(tc.tokens[6].TokenID))
  922. path = fmt.Sprintf(
  923. "%s?symbols=%s&ids=%s&limit=%d&fromItem=",
  924. endpoint, stringSymbols, stringIds, limit,
  925. )
  926. err = doGoodReqPaginated(path, historydb.OrderAsc, &tokensAPI{}, appendIter)
  927. assert.NoError(t, err)
  928. tokensFiltered = nil
  929. tokensFiltered = append(tokensFiltered, tc.tokens[2])
  930. tokensFiltered = append(tokensFiltered, tc.tokens[6])
  931. assertTokensAPIs(t, tokensFiltered, fetchedTokens)
  932. // All, in reverse order
  933. fetchedTokens = []tokenAPI{}
  934. limit = 5
  935. path = fmt.Sprintf("%s?limit=%d&fromItem=", endpoint, limit)
  936. err = doGoodReqPaginated(path, historydb.OrderDesc, &tokensAPI{}, appendIter)
  937. assert.NoError(t, err)
  938. flipedTokens := []tokenAPI{}
  939. for i := 0; i < len(tc.tokens); i++ {
  940. flipedTokens = append(flipedTokens, tc.tokens[len(tc.tokens)-1-i])
  941. }
  942. assertTokensAPIs(t, flipedTokens, fetchedTokens)
  943. }
  944. func assertTokensAPIs(t *testing.T, expected, actual []tokenAPI) {
  945. require.Equal(t, len(expected), len(actual))
  946. for i := 0; i < len(actual); i++ { //nolint len(actual) won't change within the loop
  947. actual[i].ItemID = 0
  948. if expected[i].USDUpdate == nil {
  949. assert.Equal(t, expected[i].USDUpdate, actual[i].USDUpdate)
  950. } else {
  951. assert.Equal(t, expected[i].USDUpdate.Unix(), actual[i].USDUpdate.Unix())
  952. expected[i].USDUpdate = actual[i].USDUpdate
  953. }
  954. assert.Equal(t, expected[i], actual[i])
  955. }
  956. }
  957. func doGoodReqPaginated(
  958. path, order string,
  959. iterStruct db.Paginationer,
  960. appendIter func(res interface{}),
  961. ) error {
  962. next := 0
  963. for {
  964. // Call API to get this iteration items
  965. iterPath := path
  966. if next == 0 && order == historydb.OrderDesc {
  967. // Fetch first item in reverse order
  968. iterPath += "99999"
  969. } else {
  970. // Fetch from next item or 0 if it's ascending order
  971. iterPath += strconv.Itoa(next)
  972. }
  973. if err := doGoodReq("GET", iterPath+"&order="+order, nil, iterStruct); err != nil {
  974. return err
  975. }
  976. appendIter(iterStruct)
  977. // Keep iterating?
  978. pag := iterStruct.GetPagination()
  979. if order == historydb.OrderAsc {
  980. if pag.LastReturnedItem == pag.LastItem { // No
  981. break
  982. } else { // Yes
  983. next = pag.LastReturnedItem + 1
  984. }
  985. } else {
  986. if pag.FirstReturnedItem == pag.FirstItem { // No
  987. break
  988. } else { // Yes
  989. next = pag.FirstReturnedItem - 1
  990. }
  991. }
  992. }
  993. return nil
  994. }
  995. func doGoodReq(method, path string, reqBody io.Reader, returnStruct interface{}) error {
  996. ctx := context.Background()
  997. client := &http.Client{}
  998. httpReq, _ := http.NewRequest(method, path, reqBody)
  999. route, pathParams, err := tc.router.FindRoute(httpReq.Method, httpReq.URL)
  1000. if err != nil {
  1001. return err
  1002. }
  1003. // Validate request against swagger spec
  1004. requestValidationInput := &swagger.RequestValidationInput{
  1005. Request: httpReq,
  1006. PathParams: pathParams,
  1007. Route: route,
  1008. }
  1009. if err := swagger.ValidateRequest(ctx, requestValidationInput); err != nil {
  1010. return err
  1011. }
  1012. // Do API call
  1013. resp, err := client.Do(httpReq)
  1014. if err != nil {
  1015. return err
  1016. }
  1017. if resp.Body == nil {
  1018. return errors.New("Nil body")
  1019. }
  1020. //nolint
  1021. defer resp.Body.Close()
  1022. body, err := ioutil.ReadAll(resp.Body)
  1023. if err != nil {
  1024. return err
  1025. }
  1026. if resp.StatusCode != 200 {
  1027. return fmt.Errorf("%d response: %s", resp.StatusCode, string(body))
  1028. }
  1029. // Unmarshal body into return struct
  1030. if err := json.Unmarshal(body, returnStruct); err != nil {
  1031. return err
  1032. }
  1033. // Validate response against swagger spec
  1034. responseValidationInput := &swagger.ResponseValidationInput{
  1035. RequestValidationInput: requestValidationInput,
  1036. Status: resp.StatusCode,
  1037. Header: resp.Header,
  1038. }
  1039. responseValidationInput = responseValidationInput.SetBodyBytes(body)
  1040. return swagger.ValidateResponse(ctx, responseValidationInput)
  1041. }
  1042. func doBadReq(method, path string, reqBody io.Reader, expectedResponseCode int) error {
  1043. ctx := context.Background()
  1044. client := &http.Client{}
  1045. httpReq, _ := http.NewRequest(method, path, reqBody)
  1046. route, pathParams, err := tc.router.FindRoute(httpReq.Method, httpReq.URL)
  1047. if err != nil {
  1048. return err
  1049. }
  1050. // Validate request against swagger spec
  1051. requestValidationInput := &swagger.RequestValidationInput{
  1052. Request: httpReq,
  1053. PathParams: pathParams,
  1054. Route: route,
  1055. }
  1056. if err := swagger.ValidateRequest(ctx, requestValidationInput); err != nil {
  1057. if expectedResponseCode != 400 {
  1058. return err
  1059. }
  1060. log.Warn("The request does not match the API spec")
  1061. }
  1062. // Do API call
  1063. resp, err := client.Do(httpReq)
  1064. if err != nil {
  1065. return err
  1066. }
  1067. if resp.Body == nil {
  1068. return errors.New("Nil body")
  1069. }
  1070. //nolint
  1071. defer resp.Body.Close()
  1072. body, err := ioutil.ReadAll(resp.Body)
  1073. if err != nil {
  1074. return err
  1075. }
  1076. if resp.StatusCode != expectedResponseCode {
  1077. return fmt.Errorf("Unexpected response code: %d", resp.StatusCode)
  1078. }
  1079. // Validate response against swagger spec
  1080. responseValidationInput := &swagger.ResponseValidationInput{
  1081. RequestValidationInput: requestValidationInput,
  1082. Status: resp.StatusCode,
  1083. Header: resp.Header,
  1084. }
  1085. responseValidationInput = responseValidationInput.SetBodyBytes(body)
  1086. return swagger.ValidateResponse(ctx, responseValidationInput)
  1087. }