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.

581 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. var defaultSlotSetBid [6]*big.Int = [6]*big.Int{big.NewInt(10), big.NewInt(10), big.NewInt(10), big.NewInt(10), big.NewInt(10), big.NewInt(10)}
  272. auctionVars := common.AuctionVariables{
  273. EthBlockNum: int64(2),
  274. DonationAddress: ethCommon.HexToAddress("0x1111111111111111111111111111111111111111"),
  275. DefaultSlotSetBid: defaultSlotSetBid,
  276. Outbidding: uint16(1),
  277. SlotDeadline: uint8(20),
  278. BootCoordinator: ethCommon.HexToAddress("0x1111111111111111111111111111111111111111"),
  279. ClosedAuctionSlots: uint16(2),
  280. OpenAuctionSlots: uint16(5),
  281. }
  282. rollupVars := common.RollupVariables{
  283. EthBlockNum: int64(3),
  284. FeeAddToken: big.NewInt(100),
  285. ForgeL1L2BatchTimeout: int64(44),
  286. WithdrawalDelay: uint64(3000),
  287. }
  288. wdelayerVars := common.WDelayerVariables{
  289. WithdrawalDelay: uint64(3000),
  290. }
  291. err = api.h.AddAuctionVars(&auctionVars)
  292. if err != nil {
  293. panic(err)
  294. }
  295. const nSlots = 20
  296. // Set testCommon
  297. usrTxs, allTxs := genTestTxs(sortedTxs, usrIdxs, accs, tokensUSD, blocks)
  298. 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.
  299. testBatches, fullBatches := genTestBatches(blocks, batches, allTxs)
  300. usrExits, allExits := genTestExits(exits, tokensUSD, accs, usrIdxs)
  301. tc = testCommon{
  302. blocks: blocks,
  303. tokens: tokensUSD,
  304. batches: testBatches,
  305. fullBatches: fullBatches,
  306. coordinators: coordinators,
  307. accounts: genTestAccounts(accs, tokensUSD),
  308. usrAddr: ethAddrToHez(usrAddr),
  309. usrBjj: bjjToString(usrBjj),
  310. accs: accs,
  311. usrTxs: usrTxs,
  312. allTxs: allTxs,
  313. exits: allExits,
  314. usrExits: usrExits,
  315. poolTxsToSend: poolTxsToSend,
  316. poolTxsToReceive: poolTxsToReceive,
  317. auths: genTestAuths(test.GenAuths(5)),
  318. router: router,
  319. bids: testBids,
  320. slots: api.genTestSlots(nSlots, lastBlockNum, testBids, auctionVars),
  321. auctionVars: auctionVars,
  322. rollupVars: rollupVars,
  323. wdelayerVars: wdelayerVars,
  324. }
  325. // Fake server
  326. if os.Getenv("FAKE_SERVER") == "yes" {
  327. for {
  328. log.Info("Running fake server at " + apiURL + " until ^C is received")
  329. time.Sleep(30 * time.Second)
  330. }
  331. }
  332. // Run tests
  333. result := m.Run()
  334. // Stop server
  335. if err := server.Shutdown(context.Background()); err != nil {
  336. panic(err)
  337. }
  338. if err := database.Close(); err != nil {
  339. panic(err)
  340. }
  341. if err := os.RemoveAll(dir); err != nil {
  342. panic(err)
  343. }
  344. os.Exit(result)
  345. }
  346. func doGoodReqPaginated(
  347. path, order string,
  348. iterStruct Pendinger,
  349. appendIter func(res interface{}),
  350. ) error {
  351. var next uint64
  352. firstIte := true
  353. expectedTotal := 0
  354. totalReceived := 0
  355. for {
  356. // Calculate fromItem
  357. iterPath := path
  358. if firstIte {
  359. if order == historydb.OrderDesc {
  360. // Fetch first item in reverse order
  361. iterPath += "99999" // Asumption that for testing there won't be any itemID > 99999
  362. } else {
  363. iterPath += "0"
  364. }
  365. } else {
  366. iterPath += strconv.Itoa(int(next))
  367. }
  368. // Call API to get this iteration items
  369. if err := doGoodReq("GET", iterPath+"&order="+order, nil, iterStruct); err != nil {
  370. return err
  371. }
  372. appendIter(iterStruct)
  373. // Keep iterating?
  374. remaining, lastID := iterStruct.GetPending()
  375. if remaining == 0 {
  376. break
  377. }
  378. if order == historydb.OrderDesc {
  379. next = lastID - 1
  380. } else {
  381. next = lastID + 1
  382. }
  383. // Check that the expected amount of items is consistent across iterations
  384. totalReceived += iterStruct.Len()
  385. if firstIte {
  386. firstIte = false
  387. expectedTotal = totalReceived + int(remaining)
  388. }
  389. if expectedTotal != totalReceived+int(remaining) {
  390. panic(fmt.Sprintf(
  391. "pagination error, totalReceived + remaining should be %d, but is %d",
  392. expectedTotal, totalReceived+int(remaining),
  393. ))
  394. }
  395. }
  396. return nil
  397. }
  398. func doGoodReq(method, path string, reqBody io.Reader, returnStruct interface{}) error {
  399. ctx := context.Background()
  400. client := &http.Client{}
  401. httpReq, err := http.NewRequest(method, path, reqBody)
  402. if err != nil {
  403. return err
  404. }
  405. if reqBody != nil {
  406. httpReq.Header.Add("Content-Type", "application/json")
  407. }
  408. route, pathParams, err := tc.router.FindRoute(httpReq.Method, httpReq.URL)
  409. if err != nil {
  410. return err
  411. }
  412. // Validate request against swagger spec
  413. requestValidationInput := &swagger.RequestValidationInput{
  414. Request: httpReq,
  415. PathParams: pathParams,
  416. Route: route,
  417. }
  418. if err := swagger.ValidateRequest(ctx, requestValidationInput); err != nil {
  419. return err
  420. }
  421. // Do API call
  422. resp, err := client.Do(httpReq)
  423. if err != nil {
  424. return err
  425. }
  426. if resp.Body == nil && returnStruct != nil {
  427. return errors.New("Nil body")
  428. }
  429. //nolint
  430. defer resp.Body.Close()
  431. body, err := ioutil.ReadAll(resp.Body)
  432. if err != nil {
  433. return err
  434. }
  435. if resp.StatusCode != 200 {
  436. return fmt.Errorf("%d response. Body: %s", resp.StatusCode, string(body))
  437. }
  438. if returnStruct == nil {
  439. return nil
  440. }
  441. // Unmarshal body into return struct
  442. if err := json.Unmarshal(body, returnStruct); err != nil {
  443. log.Error("invalid json: " + string(body))
  444. return err
  445. }
  446. // Validate response against swagger spec
  447. responseValidationInput := &swagger.ResponseValidationInput{
  448. RequestValidationInput: requestValidationInput,
  449. Status: resp.StatusCode,
  450. Header: resp.Header,
  451. }
  452. responseValidationInput = responseValidationInput.SetBodyBytes(body)
  453. return swagger.ValidateResponse(ctx, responseValidationInput)
  454. }
  455. func doBadReq(method, path string, reqBody io.Reader, expectedResponseCode int) error {
  456. ctx := context.Background()
  457. client := &http.Client{}
  458. httpReq, _ := http.NewRequest(method, path, reqBody)
  459. route, pathParams, err := tc.router.FindRoute(httpReq.Method, httpReq.URL)
  460. if err != nil {
  461. return err
  462. }
  463. // Validate request against swagger spec
  464. requestValidationInput := &swagger.RequestValidationInput{
  465. Request: httpReq,
  466. PathParams: pathParams,
  467. Route: route,
  468. }
  469. if err := swagger.ValidateRequest(ctx, requestValidationInput); err != nil {
  470. if expectedResponseCode != 400 {
  471. return err
  472. }
  473. log.Warn("The request does not match the API spec")
  474. }
  475. // Do API call
  476. resp, err := client.Do(httpReq)
  477. if err != nil {
  478. return err
  479. }
  480. if resp.Body == nil {
  481. return errors.New("Nil body")
  482. }
  483. //nolint
  484. defer resp.Body.Close()
  485. body, err := ioutil.ReadAll(resp.Body)
  486. if err != nil {
  487. return err
  488. }
  489. if resp.StatusCode != expectedResponseCode {
  490. return fmt.Errorf("Unexpected response code: %d. Body: %s", resp.StatusCode, string(body))
  491. }
  492. // Validate response against swagger spec
  493. responseValidationInput := &swagger.ResponseValidationInput{
  494. RequestValidationInput: requestValidationInput,
  495. Status: resp.StatusCode,
  496. Header: resp.Header,
  497. }
  498. responseValidationInput = responseValidationInput.SetBodyBytes(body)
  499. return swagger.ValidateResponse(ctx, responseValidationInput)
  500. }
  501. // test helpers
  502. func getTimestamp(blockNum int64, blocks []common.Block) time.Time {
  503. for i := 0; i < len(blocks); i++ {
  504. if blocks[i].EthBlockNum == blockNum {
  505. return blocks[i].Timestamp
  506. }
  507. }
  508. panic("timesamp not found")
  509. }
  510. func getTokenByID(id common.TokenID, tokens []historydb.TokenWithUSD) historydb.TokenWithUSD {
  511. for i := 0; i < len(tokens); i++ {
  512. if tokens[i].TokenID == id {
  513. return tokens[i]
  514. }
  515. }
  516. panic("token not found")
  517. }
  518. func getTokenByIdx(idx common.Idx, tokens []historydb.TokenWithUSD, accs []common.Account) historydb.TokenWithUSD {
  519. for _, acc := range accs {
  520. if idx == acc.Idx {
  521. return getTokenByID(acc.TokenID, tokens)
  522. }
  523. }
  524. panic("token not found")
  525. }
  526. func getAccountByIdx(idx common.Idx, accs []common.Account) *common.Account {
  527. for _, acc := range accs {
  528. if acc.Idx == idx {
  529. return &acc
  530. }
  531. }
  532. panic("account not found")
  533. }
  534. func getBlockByNum(ethBlockNum int64, blocks []common.Block) common.Block {
  535. for _, b := range blocks {
  536. if b.EthBlockNum == ethBlockNum {
  537. return b
  538. }
  539. }
  540. panic("block not found")
  541. }
  542. func getCoordinatorByBidder(bidder ethCommon.Address, coordinators []historydb.CoordinatorAPI) historydb.CoordinatorAPI {
  543. for _, c := range coordinators {
  544. if c.Bidder == bidder {
  545. return c
  546. }
  547. }
  548. panic("coordinator not found")
  549. }