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.

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