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.

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