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.

572 lines
15 KiB

3 years ago
3 years ago
3 years ago
  1. package api
  2. import (
  3. "context"
  4. "encoding/json"
  5. "errors"
  6. "fmt"
  7. "io"
  8. "io/ioutil"
  9. "math/big"
  10. "net/http"
  11. "os"
  12. "sort"
  13. "strconv"
  14. "testing"
  15. "time"
  16. ethCommon "github.com/ethereum/go-ethereum/common"
  17. swagger "github.com/getkin/kin-openapi/openapi3filter"
  18. "github.com/gin-gonic/gin"
  19. "github.com/hermeznetwork/hermez-node/common"
  20. "github.com/hermeznetwork/hermez-node/db"
  21. "github.com/hermeznetwork/hermez-node/db/historydb"
  22. "github.com/hermeznetwork/hermez-node/db/l2db"
  23. "github.com/hermeznetwork/hermez-node/db/statedb"
  24. "github.com/hermeznetwork/hermez-node/log"
  25. "github.com/hermeznetwork/hermez-node/test"
  26. "github.com/iden3/go-iden3-crypto/babyjub"
  27. )
  28. // Pendinger is an interface that allows getting last returned item ID and PendingItems to be used for building fromItem
  29. // when testing paginated endpoints.
  30. type Pendinger interface {
  31. GetPending() (pendingItems, lastItemID uint64)
  32. Len() int
  33. }
  34. const apiPort = ":4010"
  35. const apiURL = "http://localhost" + apiPort + "/"
  36. type testCommon struct {
  37. blocks []common.Block
  38. tokens []historydb.TokenWithUSD
  39. batches []testBatch
  40. fullBatches []testFullBatch
  41. coordinators []historydb.CoordinatorAPI
  42. accounts []testAccount
  43. usrAddr string
  44. usrBjj string
  45. accs []common.Account
  46. usrTxs []testTx
  47. allTxs []testTx
  48. exits []testExit
  49. usrExits []testExit
  50. poolTxsToSend []testPoolTxSend
  51. poolTxsToReceive []testPoolTxReceive
  52. auths []testAuth
  53. router *swagger.Router
  54. bids []testBid
  55. slots []testSlot
  56. auctionVars common.AuctionVariables
  57. rollupVars common.RollupVariables
  58. wdelayerVars common.WDelayerVariables
  59. }
  60. var tc testCommon
  61. var config configAPI
  62. var api *API
  63. // TestMain initializes the API server, and fill HistoryDB and StateDB with fake data,
  64. // emulating the task of the synchronizer in order to have data to be returned
  65. // by the API endpoints that will be tested
  66. func TestMain(m *testing.M) {
  67. // Initializations
  68. // Swagger
  69. router := swagger.NewRouter().WithSwaggerFromFile("./swagger.yml")
  70. // HistoryDB
  71. pass := os.Getenv("POSTGRES_PASS")
  72. database, err := db.InitSQLDB(5432, "localhost", "hermez", pass, "hermez")
  73. if err != nil {
  74. panic(err)
  75. }
  76. hdb := historydb.NewHistoryDB(database)
  77. if err != nil {
  78. panic(err)
  79. }
  80. // StateDB
  81. dir, err := ioutil.TempDir("", "tmpdb")
  82. if err != nil {
  83. panic(err)
  84. }
  85. defer func() {
  86. if err := os.RemoveAll(dir); err != nil {
  87. panic(err)
  88. }
  89. }()
  90. sdb, err := statedb.NewStateDB(dir, statedb.TypeTxSelector, 0)
  91. if err != nil {
  92. panic(err)
  93. }
  94. // L2DB
  95. l2DB := l2db.NewL2DB(database, 10, 100, 24*time.Hour)
  96. test.WipeDB(l2DB.DB()) // this will clean HistoryDB and L2DB
  97. // Config (smart contract constants)
  98. config = getConfigTest()
  99. // API
  100. apiGin := gin.Default()
  101. api, err = NewAPI(
  102. true,
  103. true,
  104. apiGin,
  105. hdb,
  106. sdb,
  107. l2DB,
  108. &config,
  109. )
  110. if err != nil {
  111. panic(err)
  112. }
  113. // Start server
  114. server := &http.Server{Addr: apiPort, Handler: apiGin}
  115. go func() {
  116. if err := server.ListenAndServe(); err != nil &&
  117. err != http.ErrServerClosed {
  118. panic(err)
  119. }
  120. }()
  121. // Fill HistoryDB and StateDB with fake data
  122. // Gen blocks and add them to DB
  123. const nBlocks = 5
  124. blocks := test.GenBlocks(1, nBlocks+1)
  125. err = api.h.AddBlocks(blocks)
  126. if err != nil {
  127. panic(err)
  128. }
  129. lastBlockNum := blocks[nBlocks-1].EthBlockNum
  130. // Gen tokens and add them to DB
  131. const nTokens = 10
  132. tokens, ethToken := test.GenTokens(nTokens, blocks)
  133. err = api.h.AddTokens(tokens)
  134. if err != nil {
  135. panic(err)
  136. }
  137. tokens = append([]common.Token{ethToken}, tokens...)
  138. // Set token value
  139. tokensUSD := []historydb.TokenWithUSD{}
  140. for i, tkn := range tokens {
  141. token := historydb.TokenWithUSD{
  142. TokenID: tkn.TokenID,
  143. EthBlockNum: tkn.EthBlockNum,
  144. EthAddr: tkn.EthAddr,
  145. Name: tkn.Name,
  146. Symbol: tkn.Symbol,
  147. Decimals: tkn.Decimals,
  148. }
  149. // Set value of 50% of the tokens
  150. if i%2 != 0 {
  151. value := float64(i) * 1.234567
  152. now := time.Now().UTC()
  153. token.USD = &value
  154. token.USDUpdate = &now
  155. err = api.h.UpdateTokenValue(token.Symbol, value)
  156. if err != nil {
  157. panic(err)
  158. }
  159. }
  160. tokensUSD = append(tokensUSD, token)
  161. }
  162. // Gen batches and add them to DB
  163. const nBatches = 10
  164. batches := test.GenBatches(nBatches, blocks)
  165. err = api.h.AddBatches(batches)
  166. if err != nil {
  167. panic(err)
  168. }
  169. // Gen accounts and add them to HistoryDB and StateDB
  170. const totalAccounts = 40
  171. const userAccounts = 4
  172. usrAddr := ethCommon.BigToAddress(big.NewInt(4896847))
  173. privK := babyjub.NewRandPrivKey()
  174. usrBjj := privK.Public()
  175. accs := test.GenAccounts(totalAccounts, userAccounts, tokens, &usrAddr, usrBjj, batches)
  176. err = api.h.AddAccounts(accs)
  177. if err != nil {
  178. panic(err)
  179. }
  180. for i := 0; i < len(accs); i++ {
  181. if _, err := api.s.CreateAccount(accs[i].Idx, &accs[i]); err != nil {
  182. panic(err)
  183. }
  184. }
  185. // helper to vinculate user related resources
  186. usrIdxs := []string{}
  187. for _, acc := range accs {
  188. if acc.EthAddr == usrAddr || acc.PublicKey == usrBjj {
  189. for _, token := range tokens {
  190. if token.TokenID == acc.TokenID {
  191. usrIdxs = append(usrIdxs, idxToHez(acc.Idx, token.Symbol))
  192. }
  193. }
  194. }
  195. }
  196. // Gen exits and add them to DB
  197. const totalExits = 40
  198. exits := test.GenExitTree(totalExits, batches, accs)
  199. err = api.h.AddExitTree(exits)
  200. if err != nil {
  201. panic(err)
  202. }
  203. // L1 and L2 txs need to be sorted in a combined way
  204. // Gen L1Txs
  205. const totalL1Txs = 40
  206. const userL1Txs = 4
  207. usrL1Txs, othrL1Txs := test.GenL1Txs(256, totalL1Txs, userL1Txs, &usrAddr, accs, tokens, blocks, batches)
  208. // Gen L2Txs
  209. const totalL2Txs = 20
  210. const userL2Txs = 4
  211. usrL2Txs, othrL2Txs := test.GenL2Txs(256+totalL1Txs, totalL2Txs, userL2Txs, &usrAddr, accs, tokens, blocks, batches)
  212. // Sort txs
  213. sortedTxs := []txSortFielder{}
  214. for i := 0; i < len(usrL1Txs); i++ {
  215. wL1 := wrappedL1(usrL1Txs[i])
  216. sortedTxs = append(sortedTxs, &wL1)
  217. }
  218. for i := 0; i < len(othrL1Txs); i++ {
  219. wL1 := wrappedL1(othrL1Txs[i])
  220. sortedTxs = append(sortedTxs, &wL1)
  221. }
  222. for i := 0; i < len(usrL2Txs); i++ {
  223. wL2 := wrappedL2(usrL2Txs[i])
  224. sortedTxs = append(sortedTxs, &wL2)
  225. }
  226. for i := 0; i < len(othrL2Txs); i++ {
  227. wL2 := wrappedL2(othrL2Txs[i])
  228. sortedTxs = append(sortedTxs, &wL2)
  229. }
  230. sort.Sort(txsSort(sortedTxs))
  231. // Store txs to DB
  232. for _, genericTx := range sortedTxs {
  233. l1 := genericTx.L1()
  234. l2 := genericTx.L2()
  235. if l1 != nil {
  236. err = api.h.AddL1Txs([]common.L1Tx{*l1})
  237. if err != nil {
  238. panic(err)
  239. }
  240. } else if l2 != nil {
  241. err = api.h.AddL2Txs([]common.L2Tx{*l2})
  242. if err != nil {
  243. panic(err)
  244. }
  245. } else {
  246. panic("should be l1 or l2")
  247. }
  248. }
  249. // Coordinators
  250. const nCoords = 10
  251. coords := test.GenCoordinators(nCoords, blocks)
  252. err = api.h.AddCoordinators(coords)
  253. if err != nil {
  254. panic(err)
  255. }
  256. fromItem := uint(0)
  257. limit := uint(99999)
  258. coordinators, _, err := api.h.GetCoordinatorsAPI(&fromItem, &limit, historydb.OrderAsc)
  259. if err != nil {
  260. panic(err)
  261. }
  262. // Bids
  263. const nBids = 20
  264. bids := test.GenBids(nBids, blocks, coords)
  265. err = api.h.AddBids(bids)
  266. if err != nil {
  267. panic(err)
  268. }
  269. testBids := genTestBids(blocks, coordinators, bids)
  270. // Vars
  271. auctionVars := common.AuctionVariables{
  272. BootCoordinator: ethCommon.HexToAddress("0x1111111111111111111111111111111111111111"),
  273. ClosedAuctionSlots: uint16(2),
  274. OpenAuctionSlots: uint16(5),
  275. }
  276. rollupVars := common.RollupVariables{
  277. WithdrawalDelay: uint64(3000),
  278. }
  279. wdelayerVars := common.WDelayerVariables{
  280. WithdrawalDelay: uint64(3000),
  281. }
  282. err = api.h.AddAuctionVars(&auctionVars)
  283. if err != nil {
  284. panic(err)
  285. }
  286. const nSlots = 20
  287. // Set testCommon
  288. usrTxs, allTxs := genTestTxs(sortedTxs, usrIdxs, accs, tokensUSD, blocks)
  289. poolTxsToSend, poolTxsToReceive := genTestPoolTx(accs, []babyjub.PrivateKey{privK}, tokensUSD) // NOTE: pool txs are not inserted to the DB here. In the test they will be posted and getted.
  290. testBatches, fullBatches := genTestBatches(blocks, batches, allTxs)
  291. usrExits, allExits := genTestExits(exits, tokensUSD, accs, usrIdxs)
  292. tc = testCommon{
  293. blocks: blocks,
  294. tokens: tokensUSD,
  295. batches: testBatches,
  296. fullBatches: fullBatches,
  297. coordinators: coordinators,
  298. accounts: genTestAccounts(accs, tokensUSD),
  299. usrAddr: ethAddrToHez(usrAddr),
  300. usrBjj: bjjToString(usrBjj),
  301. accs: accs,
  302. usrTxs: usrTxs,
  303. allTxs: allTxs,
  304. exits: allExits,
  305. usrExits: usrExits,
  306. poolTxsToSend: poolTxsToSend,
  307. poolTxsToReceive: poolTxsToReceive,
  308. auths: genTestAuths(test.GenAuths(5)),
  309. router: router,
  310. bids: testBids,
  311. slots: api.genTestSlots(nSlots, lastBlockNum, testBids, auctionVars),
  312. auctionVars: auctionVars,
  313. rollupVars: rollupVars,
  314. wdelayerVars: wdelayerVars,
  315. }
  316. // Fake server
  317. if os.Getenv("FAKE_SERVER") == "yes" {
  318. for {
  319. log.Info("Running fake server at " + apiURL + " until ^C is received")
  320. time.Sleep(30 * time.Second)
  321. }
  322. }
  323. // Run tests
  324. result := m.Run()
  325. // Stop server
  326. if err := server.Shutdown(context.Background()); err != nil {
  327. panic(err)
  328. }
  329. if err := database.Close(); err != nil {
  330. panic(err)
  331. }
  332. if err := os.RemoveAll(dir); err != nil {
  333. panic(err)
  334. }
  335. os.Exit(result)
  336. }
  337. func doGoodReqPaginated(
  338. path, order string,
  339. iterStruct Pendinger,
  340. appendIter func(res interface{}),
  341. ) error {
  342. var next uint64
  343. firstIte := true
  344. expectedTotal := 0
  345. totalReceived := 0
  346. for {
  347. // Calculate fromItem
  348. iterPath := path
  349. if firstIte {
  350. if order == historydb.OrderDesc {
  351. // Fetch first item in reverse order
  352. iterPath += "99999" // Asumption that for testing there won't be any itemID > 99999
  353. } else {
  354. iterPath += "0"
  355. }
  356. } else {
  357. iterPath += strconv.Itoa(int(next))
  358. }
  359. // Call API to get this iteration items
  360. if err := doGoodReq("GET", iterPath+"&order="+order, nil, iterStruct); err != nil {
  361. return err
  362. }
  363. appendIter(iterStruct)
  364. // Keep iterating?
  365. remaining, lastID := iterStruct.GetPending()
  366. if remaining == 0 {
  367. break
  368. }
  369. if order == historydb.OrderDesc {
  370. next = lastID - 1
  371. } else {
  372. next = lastID + 1
  373. }
  374. // Check that the expected amount of items is consistent across iterations
  375. totalReceived += iterStruct.Len()
  376. if firstIte {
  377. firstIte = false
  378. expectedTotal = totalReceived + int(remaining)
  379. }
  380. if expectedTotal != totalReceived+int(remaining) {
  381. panic(fmt.Sprintf(
  382. "pagination error, totalReceived + remaining should be %d, but is %d",
  383. expectedTotal, totalReceived+int(remaining),
  384. ))
  385. }
  386. }
  387. return nil
  388. }
  389. func doGoodReq(method, path string, reqBody io.Reader, returnStruct interface{}) error {
  390. ctx := context.Background()
  391. client := &http.Client{}
  392. httpReq, err := http.NewRequest(method, path, reqBody)
  393. if err != nil {
  394. return err
  395. }
  396. if reqBody != nil {
  397. httpReq.Header.Add("Content-Type", "application/json")
  398. }
  399. route, pathParams, err := tc.router.FindRoute(httpReq.Method, httpReq.URL)
  400. if err != nil {
  401. return err
  402. }
  403. // Validate request against swagger spec
  404. requestValidationInput := &swagger.RequestValidationInput{
  405. Request: httpReq,
  406. PathParams: pathParams,
  407. Route: route,
  408. }
  409. if err := swagger.ValidateRequest(ctx, requestValidationInput); err != nil {
  410. return err
  411. }
  412. // Do API call
  413. resp, err := client.Do(httpReq)
  414. if err != nil {
  415. return err
  416. }
  417. if resp.Body == nil && returnStruct != nil {
  418. return errors.New("Nil body")
  419. }
  420. //nolint
  421. defer resp.Body.Close()
  422. body, err := ioutil.ReadAll(resp.Body)
  423. if err != nil {
  424. return err
  425. }
  426. if resp.StatusCode != 200 {
  427. return fmt.Errorf("%d response. Body: %s", resp.StatusCode, string(body))
  428. }
  429. if returnStruct == nil {
  430. return nil
  431. }
  432. // Unmarshal body into return struct
  433. if err := json.Unmarshal(body, returnStruct); err != nil {
  434. log.Error("invalid json: " + string(body))
  435. return err
  436. }
  437. // Validate response against swagger spec
  438. responseValidationInput := &swagger.ResponseValidationInput{
  439. RequestValidationInput: requestValidationInput,
  440. Status: resp.StatusCode,
  441. Header: resp.Header,
  442. }
  443. responseValidationInput = responseValidationInput.SetBodyBytes(body)
  444. return swagger.ValidateResponse(ctx, responseValidationInput)
  445. }
  446. func doBadReq(method, path string, reqBody io.Reader, expectedResponseCode int) error {
  447. ctx := context.Background()
  448. client := &http.Client{}
  449. httpReq, _ := http.NewRequest(method, path, reqBody)
  450. route, pathParams, err := tc.router.FindRoute(httpReq.Method, httpReq.URL)
  451. if err != nil {
  452. return err
  453. }
  454. // Validate request against swagger spec
  455. requestValidationInput := &swagger.RequestValidationInput{
  456. Request: httpReq,
  457. PathParams: pathParams,
  458. Route: route,
  459. }
  460. if err := swagger.ValidateRequest(ctx, requestValidationInput); err != nil {
  461. if expectedResponseCode != 400 {
  462. return err
  463. }
  464. log.Warn("The request does not match the API spec")
  465. }
  466. // Do API call
  467. resp, err := client.Do(httpReq)
  468. if err != nil {
  469. return err
  470. }
  471. if resp.Body == nil {
  472. return errors.New("Nil body")
  473. }
  474. //nolint
  475. defer resp.Body.Close()
  476. body, err := ioutil.ReadAll(resp.Body)
  477. if err != nil {
  478. return err
  479. }
  480. if resp.StatusCode != expectedResponseCode {
  481. return fmt.Errorf("Unexpected response code: %d. Body: %s", resp.StatusCode, string(body))
  482. }
  483. // Validate response against swagger spec
  484. responseValidationInput := &swagger.ResponseValidationInput{
  485. RequestValidationInput: requestValidationInput,
  486. Status: resp.StatusCode,
  487. Header: resp.Header,
  488. }
  489. responseValidationInput = responseValidationInput.SetBodyBytes(body)
  490. return swagger.ValidateResponse(ctx, responseValidationInput)
  491. }
  492. // test helpers
  493. func getTimestamp(blockNum int64, blocks []common.Block) time.Time {
  494. for i := 0; i < len(blocks); i++ {
  495. if blocks[i].EthBlockNum == blockNum {
  496. return blocks[i].Timestamp
  497. }
  498. }
  499. panic("timesamp not found")
  500. }
  501. func getTokenByID(id common.TokenID, tokens []historydb.TokenWithUSD) historydb.TokenWithUSD {
  502. for i := 0; i < len(tokens); i++ {
  503. if tokens[i].TokenID == id {
  504. return tokens[i]
  505. }
  506. }
  507. panic("token not found")
  508. }
  509. func getTokenByIdx(idx common.Idx, tokens []historydb.TokenWithUSD, accs []common.Account) historydb.TokenWithUSD {
  510. for _, acc := range accs {
  511. if idx == acc.Idx {
  512. return getTokenByID(acc.TokenID, tokens)
  513. }
  514. }
  515. panic("token not found")
  516. }
  517. func getAccountByIdx(idx common.Idx, accs []common.Account) *common.Account {
  518. for _, acc := range accs {
  519. if acc.Idx == idx {
  520. return &acc
  521. }
  522. }
  523. panic("account not found")
  524. }
  525. func getBlockByNum(ethBlockNum int64, blocks []common.Block) common.Block {
  526. for _, b := range blocks {
  527. if b.EthBlockNum == ethBlockNum {
  528. return b
  529. }
  530. }
  531. panic("block not found")
  532. }
  533. func getCoordinatorByBidder(bidder ethCommon.Address, coordinators []historydb.CoordinatorAPI) historydb.CoordinatorAPI {
  534. for _, c := range coordinators {
  535. if c.Bidder == bidder {
  536. return c
  537. }
  538. }
  539. panic("coordinator not found")
  540. }