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.

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